Sfoglia il codice sorgente

Merge branch 'dev' of github.com:gogits/gogs into dev

Lunny Xiao 10 anni fa
parent
commit
a357cda957
100 ha cambiato i file con 5052 aggiunte e 1605 eliminazioni
  1. 0 2
      .gopmfile
  2. 3 6
      README.md
  3. 2 5
      README_ZH.md
  4. 142 12
      cmd/fix.go
  5. 30 53
      cmd/serve.go
  6. 7 17
      cmd/update.go
  7. 29 7
      cmd/web.go
  8. 12 0
      conf/app.ini
  9. 12 6
      dockerfiles/build.sh
  10. 8 2
      dockerfiles/run.sh
  11. 3 3
      gogs.go
  12. 13 12
      models/access.go
  13. 22 17
      models/action.go
  14. 0 6
      models/fix.go
  15. 6 7
      models/git_diff.go
  16. 45 45
      models/issue.go
  17. 50 57
      models/login.go
  18. 28 27
      models/models.go
  19. 16 16
      models/oauth2.go
  20. 236 0
      models/org.go
  21. 18 18
      models/publickey.go
  22. 92 29
      models/release.go
  23. 273 199
      models/repo.go
  24. 15 16
      models/update.go
  25. 134 87
      models/user.go
  26. 126 17
      models/webhook.go
  27. 57 0
      modules/auth/org.go
  28. 30 3
      modules/auth/repo.go
  29. 32 26
      modules/auth/user.go
  30. 1 0
      modules/base/base.go
  31. 262 247
      modules/bin/conf.go
  32. 27 0
      modules/cron/constantdelay.go
  33. 54 0
      modules/cron/constantdelay_test.go
  34. 203 7
      modules/cron/cron.go
  35. 255 0
      modules/cron/cron_test.go
  36. 129 0
      modules/cron/doc.go
  37. 24 0
      modules/cron/manager.go
  38. 231 0
      modules/cron/parser.go
  39. 117 0
      modules/cron/parser_test.go
  40. 161 0
      modules/cron/spec.go
  41. 173 0
      modules/cron/spec_test.go
  42. 0 95
      modules/hooks/hooks.go
  43. 13 2
      modules/log/log.go
  44. 44 35
      modules/mailer/mail.go
  45. 5 5
      modules/middleware/context.go
  46. 5 8
      modules/middleware/repo.go
  47. 89 0
      modules/process/manager.go
  48. 47 20
      modules/setting/setting.go
  49. 5 5
      modules/social/social.go
  50. 1 1
      public/css/font-awesome.min.css
  51. 247 3
      public/css/gogs.css
  52. BIN
      public/fonts/FontAwesome.otf
  53. BIN
      public/fonts/fontawesome-webfont.eot
  54. 3 165
      public/fonts/fontawesome-webfont.svg
  55. BIN
      public/fonts/fontawesome-webfont.ttf
  56. BIN
      public/fonts/fontawesome-webfont.woff
  57. 26 2
      public/js/app.js
  58. 48 9
      routers/admin/admin.go
  59. 23 18
      routers/admin/auth.go
  60. 31 22
      routers/admin/user.go
  61. 7 2
      routers/dashboard.go
  62. 2 1
      routers/dev/template.go
  63. 19 13
      routers/install.go
  64. 205 0
      routers/org/org.go
  65. 21 0
      routers/org/teams.go
  66. 8 3
      routers/repo/branch.go
  67. 48 44
      routers/repo/commit.go
  68. 20 31
      routers/repo/http.go
  69. 29 9
      routers/repo/issue.go
  70. 6 1
      routers/repo/pull.go
  71. 118 38
      routers/repo/release.go
  72. 84 22
      routers/repo/repo.go
  73. 68 41
      routers/repo/setting.go
  74. 30 11
      routers/user/home.go
  75. 18 9
      routers/user/setting.go
  76. 45 33
      routers/user/user.go
  77. 1 1
      templates/VERSION
  78. 3 3
      templates/admin/auth/edit.tmpl
  79. 0 0
      templates/admin/auth/new.tmpl
  80. 18 1
      templates/admin/config.tmpl
  81. 4 0
      templates/admin/dashboard.tmpl
  82. 40 0
      templates/admin/monitor/cron.tmpl
  83. 38 0
      templates/admin/monitor/process.tmpl
  84. 1 0
      templates/admin/nav.tmpl
  85. 0 0
      templates/admin/user/edit.tmpl
  86. 0 0
      templates/admin/user/new.tmpl
  87. 1 1
      templates/base/head.tmpl
  88. 0 0
      templates/mail/auth/active.tmpl
  89. 75 0
      templates/org/edit_team.tmpl
  90. 56 0
      templates/org/members.tmpl
  91. 32 0
      templates/org/new.tmpl
  92. 74 0
      templates/org/new_team.tmpl
  93. 85 0
      templates/org/org.tmpl
  94. 130 0
      templates/org/settings.tmpl
  95. 71 0
      templates/org/teams.tmpl
  96. 0 0
      templates/repo/branch.tmpl
  97. 30 2
      templates/repo/create.tmpl
  98. 0 0
      templates/repo/hook_add.tmpl
  99. 0 0
      templates/repo/hook_edit.tmpl
  100. 0 0
      templates/repo/issue/create.tmpl

+ 0 - 2
.gopmfile

@@ -19,8 +19,6 @@ github.com/gogits/session = `commit:7ab78d4`
 github.com/juju2013/goldap = `commit:f4a7f67`
 github.com/lib/pq = `commit:529edd9`
 github.com/nfnt/resize = `commit:8aee0d9`
-github.com/qiniu/log = `commit:891d1cb`
-github.com/robfig/cron = `commit:b024fc5`
 
 [res]
 include = templates|public

+ 3 - 6
README.md

@@ -5,11 +5,11 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### Current version: 0.4.0 Alpha
+##### Current version: 0.4.5 Alpha
 
 ### NOTICES
 
-- Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in **April 14, 2014** and will reset multiple times after. Please do **NOT** put your important data on the site.
+- Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in **June 21, 2014** and will reset multiple times after. Please do **NOT** put your important data on the site.
 - Demo site [try.gogits.org](http://try.gogits.org) is running under `dev` branch.
 
 #### Other language version
@@ -33,7 +33,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
 
 - Activity timeline
 - SSH/HTTP(S) protocol support
-- SMTP/LDAP authentication support
+- SMTP/LDAP/reverse proxy authentication support
 - Register/delete/rename account
 - Create/migrate/mirror/delete/watch/rename/transfer public/private repository
 - Repository viewer/release/issue tracker/webhooks
@@ -75,9 +75,6 @@ There are 5 ways to install Gogs:
 
 The [core team](http://gogs.io/team) of this project. See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
 
-[![Clone in Koding](http://learn.koding.com/btn/clone_d.png)][koding]
-[koding]: https://koding.com/Teamwork?import=https://github.com/gogits/gogs/archive/master.zip&c=git1
-
 ## License
 
 This project is under the MIT License. See the [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) file for the full license text.

+ 2 - 5
README_ZH.md

@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### 当前版本:0.4.0 Alpha
+##### 当前版本:0.4.5 Alpha
 
 ## 开发目的
 
@@ -24,7 +24,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
 
 - 活动时间线
 - 支持 SSH/HTTP(S) 协议
-- 支持 SMTP/LDAP 用户认证
+- 支持 SMTP/LDAP/反向代理 用户认证
 - 注册/删除/重命名用户
 - 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
 - 仓库 浏览器/发布/缺陷管理/Web 钩子
@@ -66,9 +66,6 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
 
 本项目的 [开发团队](http://gogs.io/team)。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
 
-[![Clone in Koding](http://learn.koding.com/btn/clone_d.png)][koding]
-[koding]: https://koding.com/Teamwork?import=https://github.com/gogits/gogs/archive/master.zip&c=git1
-
 ## 授权许可
 
 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。

+ 142 - 12
cmd/fix.go

@@ -5,10 +5,16 @@
 package cmd
 
 import (
+	"bufio"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"os"
+	"path"
+	"strings"
 
 	"github.com/codegangsta/cli"
+
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/setting"
 )
@@ -16,28 +22,152 @@ import (
 var CmdFix = cli.Command{
 	Name:        "fix",
 	Usage:       "This command for upgrade from old version",
-	Description: `Fix provide upgrade from old version`,
 	Action:      runFix,
+	Subcommands: fixCommands,
 	Flags:       []cli.Flag{},
 }
 
-func runFix(k *cli.Context) {
-	workDir, _ := setting.WorkDir()
-	newLogger(workDir)
+func runFix(ctx *cli.Context) {
+}
 
-	setting.NewConfigContext()
-	models.LoadModelsConfig()
+var fixCommands = []cli.Command{
+	{
+		Name:  "location",
+		Usage: "Change Gogs app location",
+		Description: `Command location fixes location change of Gogs
+
+gogs fix location <old Gogs path>
+`,
+		Action: runFixLocation,
+	},
+}
+
+// rewriteAuthorizedKeys replaces old Gogs path to the new one.
+func rewriteAuthorizedKeys(sshPath, oldPath, newPath string) error {
+	fr, err := os.Open(sshPath)
+	if err != nil {
+		return err
+	}
+	defer fr.Close()
+
+	tmpPath := sshPath + ".tmp"
+	fw, err := os.Create(tmpPath)
+	if err != nil {
+		return err
+	}
+	defer fw.Close()
+
+	oldPath = "command=\"" + oldPath + " serv"
+	newPath = "command=\"" + newPath + " serv"
+	buf := bufio.NewReader(fr)
+	for {
+		line, errRead := buf.ReadString('\n')
+		line = strings.TrimSpace(line)
+
+		if errRead != nil {
+			if errRead != io.EOF {
+				return errRead
+			}
+
+			// Reached end of file, if nothing to read then break,
+			// otherwise handle the last line.
+			if len(line) == 0 {
+				break
+			}
+		}
+
+		// Still finding the line, copy the line that currently read.
+		if _, err = fw.WriteString(strings.Replace(line, oldPath, newPath, 1) + "\n"); err != nil {
+			return err
+		}
 
-	if models.UseSQLite3 {
-		os.Chdir(workDir)
+		if errRead == io.EOF {
+			break
+		}
 	}
 
-	models.SetEngine()
+	if err = os.Remove(sshPath); err != nil {
+		return err
+	}
+	return os.Rename(tmpPath, sshPath)
+}
+
+func rewriteUpdateHook(path, appPath string) error {
+	rp := strings.NewReplacer("\\", "/", " ", "\\ ")
+	if err := ioutil.WriteFile(path, []byte(fmt.Sprintf(models.TPL_UPDATE_HOOK,
+		setting.ScriptType, rp.Replace(appPath))), os.ModePerm); err != nil {
+		return err
+	}
+	return nil
+}
 
-	err := models.Fix()
+func walkDir(rootPath, recPath, appPath string, depth int) error {
+	depth++
+	if depth > 3 {
+		return nil
+	} else if depth == 3 {
+		if err := rewriteUpdateHook(path.Join(rootPath, "hooks/update"), appPath); err != nil {
+			return err
+		}
+	}
+
+	dir, err := os.Open(rootPath)
+	if err != nil {
+		return err
+	}
+	defer dir.Close()
+
+	fis, err := dir.Readdir(0)
 	if err != nil {
+		return err
+	}
+
+	for _, fi := range fis {
+		if strings.Contains(fi.Name(), ".DS_Store") {
+			continue
+		}
+
+		relPath := path.Join(recPath, fi.Name())
+		curPath := path.Join(rootPath, fi.Name())
+		if fi.IsDir() {
+			if err = walkDir(curPath, relPath, appPath, depth); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func runFixLocation(ctx *cli.Context) {
+	if len(ctx.Args()) != 1 {
+		fmt.Println("Incorrect arguments number, expect 1")
+		os.Exit(2)
+	}
+
+	execPath, _ := setting.ExecPath()
+
+	oldPath := ctx.Args().First()
+	fmt.Printf("Old location: %s\n", oldPath)
+	fmt.Println("This command should be executed in the new Gogs path")
+	fmt.Printf("Do you want to change Gogs app path from old location to:\n")
+	fmt.Printf("-> %s?\n", execPath)
+	fmt.Print("Press <enter> to continue, use <Ctrl+c> to exit.")
+	fmt.Scanln()
+
+	// Fix in authorized_keys file.
+	sshPath := path.Join(models.SshPath, "authorized_keys")
+	fmt.Printf("Fixing pathes in file: %s\n", sshPath)
+	if err := rewriteAuthorizedKeys(sshPath, oldPath, execPath); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	// Fix position in gogs-repositories.
+	setting.NewConfigContext()
+	fmt.Printf("Fixing pathes in repositories: %s\n", setting.RepoRootPath)
+	if err := walkDir(setting.RepoRootPath, "", execPath, 0); err != nil {
 		fmt.Println(err)
-	} else {
-		fmt.Println("Fix successfully!")
+		os.Exit(1)
 	}
+	fmt.Println("Fix position finished!")
 }

+ 30 - 53
cmd/serve.go

@@ -13,9 +13,9 @@ import (
 	"strings"
 
 	"github.com/codegangsta/cli"
-	qlog "github.com/qiniu/log"
 
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
 )
 
@@ -27,27 +27,13 @@ var CmdServ = cli.Command{
 	Flags:       []cli.Flag{},
 }
 
-func newLogger(logPath string) {
-	os.MkdirAll(path.Dir(logPath), os.ModePerm)
-
-	f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
-	if err != nil {
-		qlog.Fatal(err)
-	}
-
-	qlog.SetOutput(f)
-	//qlog.SetOutputLevel(qlog.Ldebug)
-	qlog.Info("Start logging serv...")
-}
-
 func setup(logPath string) {
-	workDir, _ := setting.WorkDir()
-	newLogger(path.Join(workDir, logPath))
-
 	setting.NewConfigContext()
+	log.NewGitLogger(path.Join(setting.LogRootPath, logPath))
 	models.LoadModelsConfig()
 
 	if models.UseSQLite3 {
+		workDir, _ := setting.WorkDir()
 		os.Chdir(workDir)
 	}
 
@@ -70,45 +56,45 @@ func parseCmd(cmd string) (string, string) {
 }
 
 var (
-	COMMANDS_READONLY = map[string]int{
-		"git-upload-pack":    models.AU_WRITABLE,
-		"git upload-pack":    models.AU_WRITABLE,
-		"git-upload-archive": models.AU_WRITABLE,
+	COMMANDS_READONLY = map[string]models.AccessType{
+		"git-upload-pack":    models.WRITABLE,
+		"git upload-pack":    models.WRITABLE,
+		"git-upload-archive": models.WRITABLE,
 	}
 
-	COMMANDS_WRITE = map[string]int{
-		"git-receive-pack": models.AU_READABLE,
-		"git receive-pack": models.AU_READABLE,
+	COMMANDS_WRITE = map[string]models.AccessType{
+		"git-receive-pack": models.READABLE,
+		"git receive-pack": models.READABLE,
 	}
 )
 
-func In(b string, sl map[string]int) bool {
+func In(b string, sl map[string]models.AccessType) bool {
 	_, e := sl[b]
 	return e
 }
 
 func runServ(k *cli.Context) {
-	setup(path.Join(setting.LogRootPath, "serv.log"))
+	setup("serv.log")
 
 	keys := strings.Split(os.Args[2], "-")
 	if len(keys) != 2 {
 		println("Gogs: auth file format error")
-		qlog.Fatal("Invalid auth file format: %s", os.Args[2])
+		log.GitLogger.Fatal("Invalid auth file format: %s", os.Args[2])
 	}
 
 	keyId, err := strconv.ParseInt(keys[1], 10, 64)
 	if err != nil {
 		println("Gogs: auth file format error")
-		qlog.Fatalf("Invalid auth file format: %v", err)
+		log.GitLogger.Fatal("Invalid auth file format: %v", err)
 	}
 	user, err := models.GetUserByKeyId(keyId)
 	if err != nil {
 		if err == models.ErrUserNotKeyOwner {
 			println("Gogs: you are not the owner of SSH key")
-			qlog.Fatalf("Invalid owner of SSH key: %d", keyId)
+			log.GitLogger.Fatal("Invalid owner of SSH key: %d", keyId)
 		}
 		println("Gogs: internal error:", err)
-		qlog.Fatalf("Fail to get user by key ID(%d): %v", keyId, err)
+		log.GitLogger.Fatal("Fail to get user by key ID(%d): %v", keyId, err)
 	}
 
 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
@@ -122,7 +108,7 @@ func runServ(k *cli.Context) {
 	rr := strings.SplitN(repoPath, "/", 2)
 	if len(rr) != 2 {
 		println("Gogs: unavailable repository", args)
-		qlog.Fatalf("Unavailable repository: %v", args)
+		log.GitLogger.Fatal("Unavailable repository: %v", args)
 	}
 	repoUserName := rr[0]
 	repoName := strings.TrimSuffix(rr[1], ".git")
@@ -134,45 +120,45 @@ func runServ(k *cli.Context) {
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			println("Gogs: given repository owner are not registered")
-			qlog.Fatalf("Unregistered owner: %s", repoUserName)
+			log.GitLogger.Fatal("Unregistered owner: %s", repoUserName)
 		}
 		println("Gogs: internal error:", err)
-		qlog.Fatalf("Fail to get repository owner(%s): %v", repoUserName, err)
+		log.GitLogger.Fatal("Fail to get repository owner(%s): %v", repoUserName, err)
 	}
 
 	// Access check.
 	switch {
 	case isWrite:
-		has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_WRITABLE)
+		has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE)
 		if err != nil {
 			println("Gogs: internal error:", err)
-			qlog.Fatal("Fail to check write access:", err)
+			log.GitLogger.Fatal("Fail to check write access:", err)
 		} else if !has {
 			println("You have no right to write this repository")
-			qlog.Fatalf("User %s has no right to write repository %s", user.Name, repoPath)
+			log.GitLogger.Fatal("User %s has no right to write repository %s", user.Name, repoPath)
 		}
 	case isRead:
 		repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
 		if err != nil {
 			if err == models.ErrRepoNotExist {
 				println("Gogs: given repository does not exist")
-				qlog.Fatalf("Repository does not exist: %s/%s", repoUser.Name, repoName)
+				log.GitLogger.Fatal("Repository does not exist: %s/%s", repoUser.Name, repoName)
 			}
 			println("Gogs: internal error:", err)
-			qlog.Fatalf("Fail to get repository: %v", err)
+			log.GitLogger.Fatal("Fail to get repository: %v", err)
 		}
 
 		if !repo.IsPrivate {
 			break
 		}
 
-		has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_READABLE)
+		has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE)
 		if err != nil {
 			println("Gogs: internal error:", err)
-			qlog.Fatal("Fail to check read access:", err)
+			log.GitLogger.Fatal("Fail to check read access:", err)
 		} else if !has {
 			println("You have no right to access this repository")
-			qlog.Fatalf("User %s has no right to read repository %s", user.Name, repoPath)
+			log.GitLogger.Fatal("User %s has no right to read repository %s", user.Name, repoPath)
 		}
 	default:
 		println("Unknown command")
@@ -186,18 +172,9 @@ func runServ(k *cli.Context) {
 	gitcmd.Stdout = os.Stdout
 	gitcmd.Stdin = os.Stdin
 	gitcmd.Stderr = os.Stderr
-
-	if err = gitcmd.Run(); err != nil {
+	err = gitcmd.Run()
+	if err != nil {
 		println("Gogs: internal error:", err)
-		qlog.Fatalf("Fail to execute git command: %v", err)
+		log.GitLogger.Fatal("Fail to execute git command: %v", err)
 	}
-
-	//refName := os.Getenv("refName")
-	//oldCommitId := os.Getenv("oldCommitId")
-	//newCommitId := os.Getenv("newCommitId")
-
-	//qlog.Error("get envs:", refName, oldCommitId, newCommitId)
-
-	// update
-	//models.Update(refName, oldCommitId, newCommitId, repoUserName, repoName, user.Id)
 }

+ 7 - 17
cmd/update.go

@@ -6,14 +6,12 @@ package cmd
 
 import (
 	"os"
-	"path"
 	"strconv"
 
 	"github.com/codegangsta/cli"
-	qlog "github.com/qiniu/log"
 
 	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/modules/setting"
+	"github.com/gogits/gogs/modules/log"
 )
 
 var CmdUpdate = cli.Command{
@@ -24,35 +22,27 @@ var CmdUpdate = cli.Command{
 	Flags:       []cli.Flag{},
 }
 
-func updateEnv(refName, oldCommitId, newCommitId string) {
-	os.Setenv("refName", refName)
-	os.Setenv("oldCommitId", oldCommitId)
-	os.Setenv("newCommitId", newCommitId)
-	qlog.Info("set envs:", refName, oldCommitId, newCommitId)
-}
-
 func runUpdate(c *cli.Context) {
 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
 	if cmd == "" {
 		return
 	}
 
-	setup(path.Join(setting.LogRootPath, "update.log"))
+	setup("update.log")
 
 	args := c.Args()
 	if len(args) != 3 {
-		qlog.Fatal("received less 3 parameters")
+		log.GitLogger.Fatal("received less 3 parameters")
 	} else if args[0] == "" {
-		qlog.Fatal("refName is empty, shouldn't use")
+		log.GitLogger.Fatal("refName is empty, shouldn't use")
 	}
 
-	//updateEnv(args[0], args[1], args[2])
-
 	userName := os.Getenv("userName")
 	userId, _ := strconv.ParseInt(os.Getenv("userId"), 10, 64)
-	//repoId := os.Getenv("repoId")
 	repoUserName := os.Getenv("repoUserName")
 	repoName := os.Getenv("repoName")
 
-	models.Update(args[0], args[1], args[2], userName, repoUserName, repoName, userId)
+	if err := models.Update(args[0], args[1], args[2], userName, repoUserName, repoName, userId); err != nil {
+		log.GitLogger.Fatal(err.Error())
+	}
 }

+ 29 - 7
cmd/web.go

@@ -27,6 +27,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/repo"
 	"github.com/gogits/gogs/routers/user"
 )
@@ -89,11 +90,13 @@ func runWeb(*cli.Context) {
 	m.Get("/", ignSignIn, routers.Home)
 	m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
 	m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
-	m.Get("/issues", reqSignIn, user.Issues)
-	m.Get("/pulls", reqSignIn, user.Pulls)
-	m.Get("/stars", reqSignIn, user.Stars)
+	m.Group("", func(r martini.Router) {
+		r.Get("/issues", user.Issues)
+		r.Get("/pulls", user.Pulls)
+		r.Get("/stars", user.Stars)
+	}, reqSignIn)
 
-	m.Group("/api", func(r martini.Router) {
+	m.Group("/api", func(_ martini.Router) {
 		m.Group("/v1", func(r martini.Router) {
 			// Miscellaneous.
 			r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
@@ -159,8 +162,9 @@ func runWeb(*cli.Context) {
 	m.Group("/admin", func(r martini.Router) {
 		r.Get("/users", admin.Users)
 		r.Get("/repos", admin.Repositories)
-		r.Get("/config", admin.Config)
 		r.Get("/auths", admin.Auths)
+		r.Get("/config", admin.Config)
+		r.Get("/monitor", admin.Monitor)
 	}, adminReq)
 	m.Group("/admin/users", func(r martini.Router) {
 		r.Get("/new", admin.NewUser)
@@ -184,6 +188,22 @@ func runWeb(*cli.Context) {
 
 	reqOwner := middleware.RequireOwner()
 
+	m.Group("/org", func(r martini.Router) {
+		r.Get("/create", org.New)
+		r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost)
+		r.Get("/:org", org.Organization)
+		r.Get("/:org/dashboard", org.Dashboard)
+		r.Get("/:org/members", org.Members)
+
+		r.Get("/:org/teams/:team/edit", org.EditTeam)
+		r.Get("/:org/teams/new", org.NewTeam)
+		r.Get("/:org/teams", org.Teams)
+
+		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 martini.Router) {
 		r.Get("/settings", repo.Setting)
 		r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
@@ -221,11 +241,13 @@ func runWeb(*cli.Context) {
 		})
 
 		r.Post("/comment/:action", repo.Comment)
-		r.Get("/releases/new", repo.ReleasesNew)
+		r.Get("/releases/new", repo.NewRelease)
+		r.Get("/releases/edit/:tagname", repo.EditRelease)
 	}, reqSignIn, middleware.RepoAssignment(true))
 
 	m.Group("/:username/:reponame", func(r martini.Router) {
-		r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.ReleasesNewPost)
+		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 martini.Router) {

+ 12 - 0
conf/app.ini

@@ -51,6 +51,8 @@ SECRET_KEY = !#@FDEWREWR&*(
 LOGIN_REMEMBER_DAYS = 7
 COOKIE_USERNAME = gogs_awesome
 COOKIE_REMEMBER_NAME = gogs_incredible
+; Reverse proxy authentication header name of user name
+REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
 
 [service]
 ACTIVE_CODE_LIVE_MINUTES = 180
@@ -65,6 +67,14 @@ REQUIRE_SIGNIN_VIEW = false
 ENABLE_CACHE_AVATAR = false
 ; Mail notification
 ENABLE_NOTIFY_MAIL = false
+; More detail: https://github.com/gogits/gogs/issues/165
+ENABLE_REVERSE_PROXY_AUTHENTICATION = false
+
+[webhook]
+; Cron task interval in minutes
+TASK_INTERVAL = 1
+; Deliver timeout in seconds
+DELIVER_TIMEOUT = 5
 
 [mailer]
 ENABLED = false
@@ -227,5 +237,7 @@ RECEIVERS =
 ; For "database" mode only
 [log.database]
 LEVEL = 
+; Either "mysql" or "postgres"
 DRIVER = 
+; Based on xorm, e.g.: root:root@localhost/gogs?charset=utf8
 CONN = 

+ 12 - 6
dockerfiles/build.sh

@@ -10,6 +10,12 @@ HOST_PORT="YOUR_HOST_PORT"        # The port on host, which will be redirected t
 # apt source, you can select 'nchc'(mirror in Taiwan) or 'aliyun'(best for mainlance China users) according to your network, if you could connect to the official unbunt mirror in a fast speed, just leave it to "".
 APT_SOURCE=""
 
+DOCKER_BIN=$(which docker.io || which docker)
+if [ -z "$DOCKER_BIN" ] ; then
+    echo "Please install docker. You can install docker by running \"wget -qO- https://get.docker.io/ | sh\"."
+    exit 1
+fi
+
 # Replace the database root password in database image Dockerfile.
 sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/$DB_TYPE/Dockerfile
 # Replace the database root password in gogits image deploy.sh file. 
@@ -36,22 +42,22 @@ if [ $MEM_TYPE != "" ]
   sed -i "${GOGS_BUILD_LINE}s/$/ -tags $MEM_TYPE/" images/gogits/Dockerfile
 
   cd images/$MEM_TYPE
-  docker build -t gogits/$MEM_TYPE .
-  docker run -d --name $MEM_RUN_NAME gogits/$MEM_TYPE
+  $DOCKER_BIN build -t gogits/$MEM_TYPE .
+  $DOCKER_BIN run -d --name $MEM_RUN_NAME gogits/$MEM_TYPE
   MEM_LINK=" --link $MEM_RUN_NAME:mem "
   cd ../../
 fi
 
 # Build the database image
 cd images/$DB_TYPE
-docker build -t gogits/$DB_TYPE .
+$DOCKER_BIN build -t gogits/$DB_TYPE .
 #
 
 
 ## Build the gogits image
 cd ../gogits
 
-docker build -t gogits/gogs .
+$DOCKER_BIN build -t gogits/gogs .
 
 #sed -i "s#RUN go get -u -tags $MEM_TYPE github.com/gogits/gogs#RUN go get -u github.com/gogits/gogs#g" Dockerfile
 
@@ -60,9 +66,9 @@ sed -i "s/ -tags $MEM_TYPE//" Dockerfile
 
 #
 ## Run MySQL image with name
-docker run -d --name $DB_RUN_NAME gogits/$DB_TYPE
+$DOCKER_BIN run -d --name $DB_RUN_NAME gogits/$DB_TYPE
 #
 ## Run gogits image and link it to the database image
 echo "Now we have the $DB_TYPE image(running) and gogs image, use the follow command to start gogs service:"
-echo -e "\033[33m docker run -i -t --link $DB_RUN_NAME:db $MEM_LINK -p $HOST_PORT:3000 gogits/gogs \033[0m"
+echo -e "\033[33m $DOCKER_BIN run -i -t --link $DB_RUN_NAME:db $MEM_LINK -p $HOST_PORT:3000 gogits/gogs \033[0m"
 

+ 8 - 2
dockerfiles/run.sh

@@ -5,9 +5,15 @@ typeset -u MYSQL_ALIAS
 MYSQL_ALIAS="db"
 HOST_PORT="3000"
 
+DOCKER_BIN=$(which docker.io || which docker)
+if [ -z "$DOCKER_BIN" ] ; then
+    echo "Please install docker. You can install docker by running \"wget -qO- https://get.docker.io/ | sh\"."
+    exit 1
+fi
+
 ## Run MySQL image with name
-docker run -d --name $MYSQL_RUN_NAME gogs/mysql
+$DOCKER_BIN run -d --name $MYSQL_RUN_NAME gogs/mysql
 #
 ## Run gogits image and link it to the MySQL image
-docker run --link $MYSQL_RUN_NAME:$MYSQL_ALIAS -p $HOST_PORT:3000 gogs/gogits
+$DOCKER_BIN run --link $MYSQL_RUN_NAME:$MYSQL_ALIAS -p $HOST_PORT:3000 gogs/gogits
 

+ 3 - 3
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.4.1.0601 Alpha"
+const APP_VER = "0.4.5.0628 Alpha"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())
@@ -31,10 +31,10 @@ func main() {
 	app.Version = APP_VER
 	app.Commands = []cli.Command{
 		cmd.CmdWeb,
-		// cmd.CmdFix,
-		cmd.CmdDump,
 		cmd.CmdServ,
 		cmd.CmdUpdate,
+		cmd.CmdFix,
+		cmd.CmdDump,
 	}
 	app.Flags = append(app.Flags, []cli.Flag{}...)
 	app.Run(os.Args)

+ 13 - 12
models/access.go

@@ -11,26 +11,27 @@ import (
 	"github.com/go-xorm/xorm"
 )
 
-// Access types.
+type AccessType int
+
 const (
-	AU_READABLE = iota + 1
-	AU_WRITABLE
+	READABLE AccessType = iota + 1
+	WRITABLE
 )
 
 // Access represents the accessibility of user to repository.
 type Access struct {
 	Id       int64
-	UserName string    `xorm:"unique(s)"`
-	RepoName string    `xorm:"unique(s)"` // <user name>/<repo name>
-	Mode     int       `xorm:"unique(s)"`
-	Created  time.Time `xorm:"created"`
+	UserName string     `xorm:"unique(s)"`
+	RepoName string     `xorm:"unique(s)"` // <user name>/<repo name>
+	Mode     AccessType `xorm:"unique(s)"`
+	Created  time.Time  `xorm:"created"`
 }
 
 // AddAccess adds new access record.
 func AddAccess(access *Access) error {
 	access.UserName = strings.ToLower(access.UserName)
 	access.RepoName = strings.ToLower(access.RepoName)
-	_, err := orm.Insert(access)
+	_, err := x.Insert(access)
 	return err
 }
 
@@ -38,13 +39,13 @@ func AddAccess(access *Access) error {
 func UpdateAccess(access *Access) error {
 	access.UserName = strings.ToLower(access.UserName)
 	access.RepoName = strings.ToLower(access.RepoName)
-	_, err := orm.Id(access.Id).Update(access)
+	_, err := x.Id(access.Id).Update(access)
 	return err
 }
 
 // DeleteAccess deletes access record.
 func DeleteAccess(access *Access) error {
-	_, err := orm.Delete(access)
+	_, err := x.Delete(access)
 	return err
 }
 
@@ -59,7 +60,7 @@ func UpdateAccessWithSession(sess *xorm.Session, access *Access) error {
 
 // HasAccess returns true if someone can read or write to given repository.
 // The repoName should be in format <username>/<reponame>.
-func HasAccess(uname, repoName string, mode int) (bool, error) {
+func HasAccess(uname, repoName string, mode AccessType) (bool, error) {
 	if len(repoName) == 0 {
 		return false, nil
 	}
@@ -67,7 +68,7 @@ func HasAccess(uname, repoName string, mode int) (bool, error) {
 		UserName: strings.ToLower(uname),
 		RepoName: strings.ToLower(repoName),
 	}
-	has, err := orm.Get(access)
+	has, err := x.Get(access)
 	if err != nil {
 		return false, err
 	} else if !has {

+ 22 - 17
models/action.go

@@ -12,10 +12,8 @@ import (
 	"time"
 
 	"github.com/gogits/git"
-	qlog "github.com/qiniu/log"
 
 	"github.com/gogits/gogs/modules/base"
-	"github.com/gogits/gogs/modules/hooks"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
 )
@@ -116,7 +114,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
 		return errors.New("action.CommitRepoAction(NotifyWatchers): " + err.Error())
 
 	}
-	qlog.Info("action.CommitRepoAction(end): %d/%s", repoUserId, repoName)
+	//qlog.Info("action.CommitRepoAction(end): %d/%s", repoUserId, repoName)
 
 	// New push event hook.
 	if err := repo.GetOwner(); err != nil {
@@ -131,35 +129,35 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
 	}
 
 	repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
-	commits := make([]*hooks.PayloadCommit, len(commit.Commits))
+	commits := make([]*PayloadCommit, len(commit.Commits))
 	for i, cmt := range commit.Commits {
-		commits[i] = &hooks.PayloadCommit{
+		commits[i] = &PayloadCommit{
 			Id:      cmt.Sha1,
 			Message: cmt.Message,
 			Url:     fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
-			Author: &hooks.PayloadAuthor{
+			Author: &PayloadAuthor{
 				Name:  cmt.AuthorName,
 				Email: cmt.AuthorEmail,
 			},
 		}
 	}
-	p := &hooks.Payload{
+	p := &Payload{
 		Ref:     refFullName,
 		Commits: commits,
-		Repo: &hooks.PayloadRepo{
+		Repo: &PayloadRepo{
 			Id:          repo.Id,
 			Name:        repo.LowerName,
 			Url:         repoLink,
 			Description: repo.Description,
 			Website:     repo.Website,
 			Watchers:    repo.NumWatches,
-			Owner: &hooks.PayloadAuthor{
+			Owner: &PayloadAuthor{
 				Name:  repoUserName,
 				Email: actEmail,
 			},
 			Private: repo.IsPrivate,
 		},
-		Pusher: &hooks.PayloadAuthor{
+		Pusher: &PayloadAuthor{
 			Name:  repo.Owner.LowerName,
 			Email: repo.Owner.Email,
 		},
@@ -172,20 +170,27 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
 		}
 
 		p.Secret = w.Secret
-		hooks.AddHookTask(&hooks.HookTask{hooks.HTT_WEBHOOK, w.Url, p, w.ContentType, w.IsSsl})
+		CreateHookTask(&HookTask{
+			Type:        WEBHOOK,
+			Url:         w.Url,
+			Payload:     p,
+			ContentType: w.ContentType,
+			IsSsl:       w.IsSsl,
+		})
 	}
 	return nil
 }
 
 // NewRepoAction adds new action for creating repository.
-func NewRepoAction(user *User, repo *Repository) (err error) {
-	if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
-		OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name, IsPrivate: repo.IsPrivate}); err != nil {
-		log.Error("action.NewRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
+func NewRepoAction(u *User, repo *Repository) (err error) {
+	if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email,
+		OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoUserName: repo.Owner.Name, RepoName: repo.Name,
+		IsPrivate: repo.IsPrivate}); err != nil {
+		log.Error("action.NewRepoAction(notify watchers): %d/%s", u.Id, repo.Name)
 		return err
 	}
 
-	log.Trace("action.NewRepoAction: %s/%s", user.LowerName, repo.LowerName)
+	log.Trace("action.NewRepoAction: %s/%s", u.LowerName, repo.LowerName)
 	return err
 }
 
@@ -205,7 +210,7 @@ func TransferRepoAction(user, newUser *User, repo *Repository) (err error) {
 // GetFeeds returns action list of given user in given context.
 func GetFeeds(userid, offset int64, isProfile bool) ([]*Action, error) {
 	actions := make([]*Action, 0, 20)
-	sess := orm.Limit(20, int(offset)).Desc("id").Where("user_id=?", userid)
+	sess := x.Limit(20, int(offset)).Desc("id").Where("user_id=?", userid)
 	if isProfile {
 		sess.Where("is_private=?", false).And("act_user_id=?", userid)
 	} else {

+ 0 - 6
models/fix.go

@@ -1,6 +0,0 @@
-package models
-
-func Fix() error {
-	_, err := orm.Exec("alter table repository drop column num_releases")
-	return err
-}

+ 6 - 7
models/git_diff.go

@@ -6,6 +6,7 @@ package models
 
 import (
 	"bufio"
+	"fmt"
 	"io"
 	"os"
 	"os/exec"
@@ -15,6 +16,7 @@ import (
 
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/process"
 )
 
 // Diff line types.
@@ -67,7 +69,7 @@ func (diff *Diff) NumFiles() int {
 
 const DIFF_HEAD = "diff --git "
 
-func ParsePatch(cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
+func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
 	scanner := bufio.NewScanner(reader)
 	var (
 		curFile    *DiffFile
@@ -169,11 +171,8 @@ func ParsePatch(cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
 	}
 
 	// In case process became zombie.
-	if !cmd.ProcessState.Exited() {
-		log.Debug("git_diff.ParsePatch: process doesn't exit and now will be killed")
-		if err := cmd.Process.Kill(); err != nil {
-			log.Error("git_diff.ParsePatch: fail to kill zombie process: %v", err)
-		}
+	if err := process.Kill(pid); err != nil {
+		log.Error("git_diff.ParsePatch(Kill): %v", err)
 	}
 	return diff, nil
 }
@@ -207,5 +206,5 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
 		wr.Close()
 	}()
 	defer rd.Close()
-	return ParsePatch(cmd, rd)
+	return ParsePatch(process.Add(fmt.Sprintf("GetDiff(%s)", repoPath), cmd), cmd, rd)
 }

+ 45 - 45
models/issue.go

@@ -92,7 +92,7 @@ func (i *Issue) GetAssignee() (err error) {
 
 // CreateIssue creates new issue for repository.
 func NewIssue(issue *Issue) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -114,7 +114,7 @@ func NewIssue(issue *Issue) (err error) {
 // GetIssueByIndex returns issue by given index in repository.
 func GetIssueByIndex(rid, index int64) (*Issue, error) {
 	issue := &Issue{RepoId: rid, Index: index}
-	has, err := orm.Get(issue)
+	has, err := x.Get(issue)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -126,7 +126,7 @@ func GetIssueByIndex(rid, index int64) (*Issue, error) {
 // GetIssueById returns an issue by ID.
 func GetIssueById(id int64) (*Issue, error) {
 	issue := &Issue{Id: id}
-	has, err := orm.Get(issue)
+	has, err := x.Get(issue)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -137,7 +137,7 @@ func GetIssueById(id int64) (*Issue, error) {
 
 // GetIssues returns a list of issues by given conditions.
 func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
-	sess := orm.Limit(20, (page-1)*20)
+	sess := x.Limit(20, (page-1)*20)
 
 	if rid > 0 {
 		sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
@@ -193,13 +193,13 @@ const (
 // GetIssuesByLabel returns a list of issues by given label and repository.
 func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
 	issues := make([]*Issue, 0, 10)
-	err := orm.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
+	err := x.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
 	return issues, err
 }
 
 // GetIssueCountByPoster returns number of issues of repository by poster.
 func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
-	count, _ := orm.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
+	count, _ := x.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
 	return count
 }
 
@@ -213,9 +213,9 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
 // IssueUser represents an issue-user relation.
 type IssueUser struct {
 	Id          int64
-	Uid         int64 // User ID.
+	Uid         int64 `xorm:"INDEX"` // User ID.
 	IssueId     int64
-	RepoId      int64
+	RepoId      int64 `xorm:"INDEX"`
 	MilestoneId int64
 	IsRead      bool
 	IsAssigned  bool
@@ -241,7 +241,7 @@ func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err erro
 			isNeedAddPoster = false
 		}
 		iu.IsAssigned = iu.Uid == aid
-		if _, err = orm.Insert(iu); err != nil {
+		if _, err = x.Insert(iu); err != nil {
 			return err
 		}
 	}
@@ -249,7 +249,7 @@ func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err erro
 		iu.Uid = pid
 		iu.IsPoster = true
 		iu.IsAssigned = iu.Uid == aid
-		if _, err = orm.Insert(iu); err != nil {
+		if _, err = x.Insert(iu); err != nil {
 			return err
 		}
 	}
@@ -270,7 +270,7 @@ func PairsContains(ius []*IssueUser, issueId int64) int {
 // GetIssueUserPairs returns issue-user pairs by given repository and user.
 func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
 	ius := make([]*IssueUser, 0, 10)
-	err := orm.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoId: rid, Uid: uid})
+	err := x.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoId: rid, Uid: uid})
 	return ius, err
 }
 
@@ -285,7 +285,7 @@ func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*Issue
 	cond := strings.TrimSuffix(buf.String(), " OR ")
 
 	ius := make([]*IssueUser, 0, 10)
-	sess := orm.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
+	sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
 	if len(cond) > 0 {
 		sess.And(cond)
 	}
@@ -296,7 +296,7 @@ func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*Issue
 // GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
 func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
 	ius := make([]*IssueUser, 0, 10)
-	sess := orm.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
+	sess := x.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
 	if rid > 0 {
 		sess.And("repo_id=?", rid)
 	}
@@ -335,7 +335,7 @@ func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStat
 	issue := new(Issue)
 	tmpSess := &xorm.Session{}
 
-	sess := orm.Where("repo_id=?", rid)
+	sess := x.Where("repo_id=?", rid)
 	*tmpSess = *sess
 	stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
 	*tmpSess = *sess
@@ -347,7 +347,7 @@ func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStat
 	}
 
 	if filterMode != FM_MENTION {
-		sess = orm.Where("repo_id=?", rid)
+		sess = x.Where("repo_id=?", rid)
 		switch filterMode {
 		case FM_ASSIGN:
 			sess.And("assignee_id=?", uid)
@@ -361,16 +361,16 @@ func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStat
 		*tmpSess = *sess
 		stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
 	} else {
-		sess := orm.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
+		sess := x.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
 		*tmpSess = *sess
 		stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
 		*tmpSess = *sess
 		stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
 	}
 nofilter:
-	stats.AssignCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
-	stats.CreateCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
-	stats.MentionCount, _ = orm.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
+	stats.AssignCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
+	stats.CreateCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
+	stats.MentionCount, _ = x.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
 	return stats
 }
 
@@ -378,28 +378,28 @@ nofilter:
 func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
 	stats := &IssueStats{}
 	issue := new(Issue)
-	stats.AssignCount, _ = orm.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
-	stats.CreateCount, _ = orm.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
+	stats.AssignCount, _ = x.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
+	stats.CreateCount, _ = x.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
 	return stats
 }
 
 // UpdateIssue updates information of issue.
 func UpdateIssue(issue *Issue) error {
-	_, err := orm.Id(issue.Id).AllCols().Update(issue)
+	_, err := x.Id(issue.Id).AllCols().Update(issue)
 	return err
 }
 
 // UpdateIssueUserByStatus updates issue-user pairs by issue status.
 func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
 	rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
-	_, err := orm.Exec(rawSql, isClosed, iid)
+	_, err := x.Exec(rawSql, isClosed, iid)
 	return err
 }
 
 // UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
 func UpdateIssueUserPairByAssignee(aid, iid int64) error {
 	rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
-	if _, err := orm.Exec(rawSql, false, iid); err != nil {
+	if _, err := x.Exec(rawSql, false, iid); err != nil {
 		return err
 	}
 
@@ -408,14 +408,14 @@ func UpdateIssueUserPairByAssignee(aid, iid int64) error {
 		return nil
 	}
 	rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
-	_, err := orm.Exec(rawSql, aid, iid)
+	_, err := x.Exec(rawSql, aid, iid)
 	return err
 }
 
 // UpdateIssueUserPairByRead updates issue-user pair for reading.
 func UpdateIssueUserPairByRead(uid, iid int64) error {
 	rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
-	_, err := orm.Exec(rawSql, true, uid, iid)
+	_, err := x.Exec(rawSql, true, uid, iid)
 	return err
 }
 
@@ -423,16 +423,16 @@ func UpdateIssueUserPairByRead(uid, iid int64) error {
 func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
 	for _, uid := range uids {
 		iu := &IssueUser{Uid: uid, IssueId: iid}
-		has, err := orm.Get(iu)
+		has, err := x.Get(iu)
 		if err != nil {
 			return err
 		}
 
 		iu.IsMentioned = true
 		if has {
-			_, err = orm.Id(iu.Id).AllCols().Update(iu)
+			_, err = x.Id(iu.Id).AllCols().Update(iu)
 		} else {
-			_, err = orm.Insert(iu)
+			_, err = x.Insert(iu)
 		}
 		if err != nil {
 			return err
@@ -467,7 +467,7 @@ func (m *Label) CalOpenIssues() {
 
 // NewLabel creates new label of repository.
 func NewLabel(l *Label) error {
-	_, err := orm.Insert(l)
+	_, err := x.Insert(l)
 	return err
 }
 
@@ -478,7 +478,7 @@ func GetLabelById(id int64) (*Label, error) {
 	}
 
 	l := &Label{Id: id}
-	has, err := orm.Get(l)
+	has, err := x.Get(l)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -490,13 +490,13 @@ func GetLabelById(id int64) (*Label, error) {
 // GetLabels returns a list of labels of given repository ID.
 func GetLabels(repoId int64) ([]*Label, error) {
 	labels := make([]*Label, 0, 10)
-	err := orm.Where("repo_id=?", repoId).Find(&labels)
+	err := x.Where("repo_id=?", repoId).Find(&labels)
 	return labels, err
 }
 
 // UpdateLabel updates label information.
 func UpdateLabel(l *Label) error {
-	_, err := orm.Id(l.Id).Update(l)
+	_, err := x.Id(l.Id).Update(l)
 	return err
 }
 
@@ -516,7 +516,7 @@ func DeleteLabel(repoId int64, strId string) error {
 		return err
 	}
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -569,7 +569,7 @@ func (m *Milestone) CalOpenIssues() {
 
 // NewMilestone creates new milestone of repository.
 func NewMilestone(m *Milestone) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -591,7 +591,7 @@ func NewMilestone(m *Milestone) (err error) {
 // GetMilestoneById returns the milestone by given ID.
 func GetMilestoneById(id int64) (*Milestone, error) {
 	m := &Milestone{Id: id}
-	has, err := orm.Get(m)
+	has, err := x.Get(m)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -603,7 +603,7 @@ func GetMilestoneById(id int64) (*Milestone, error) {
 // GetMilestoneByIndex returns the milestone of given repository and index.
 func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
 	m := &Milestone{RepoId: repoId, Index: idx}
-	has, err := orm.Get(m)
+	has, err := x.Get(m)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -615,13 +615,13 @@ func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
 // GetMilestones returns a list of milestones of given repository and status.
 func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
 	miles := make([]*Milestone, 0, 10)
-	err := orm.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
+	err := x.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
 	return miles, err
 }
 
 // UpdateMilestone updates information of given milestone.
 func UpdateMilestone(m *Milestone) error {
-	_, err := orm.Id(m.Id).Update(m)
+	_, err := x.Id(m.Id).Update(m)
 	return err
 }
 
@@ -632,7 +632,7 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
 		return err
 	}
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -658,7 +658,7 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
 
 // ChangeMilestoneAssign changes assignment of milestone for issue.
 func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -717,7 +717,7 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
 
 // DeleteMilestone deletes a milestone.
 func DeleteMilestone(m *Milestone) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -771,13 +771,13 @@ type Comment struct {
 	IssueId  int64
 	CommitId int64
 	Line     int64
-	Content  string
+	Content  string    `xorm:"TEXT"`
 	Created  time.Time `xorm:"CREATED"`
 }
 
 // CreateComment creates comment of issue or commit.
 func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err := sess.Begin(); err != nil {
 		return err
@@ -816,6 +816,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, c
 // GetIssueComments returns list of comment by given issue id.
 func GetIssueComments(issueId int64) ([]Comment, error) {
 	comments := make([]Comment, 0, 10)
-	err := orm.Asc("created").Find(&comments, &Comment{IssueId: issueId})
+	err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
 	return comments, err
 }

+ 50 - 57
models/login.go

@@ -1,4 +1,4 @@
-// Copyright github.com/juju2013. All rights reserved.
+// Copyright 2014 The Gogs Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
@@ -20,12 +20,13 @@ import (
 	"github.com/gogits/gogs/modules/log"
 )
 
-// Login types.
+type LoginType int
+
 const (
-	LT_NOTYPE = iota
-	LT_PLAIN
-	LT_LDAP
-	LT_SMTP
+	NOTYPE LoginType = iota
+	PLAIN
+	LDAP
+	SMTP
 )
 
 var (
@@ -34,9 +35,9 @@ var (
 	ErrAuthenticationUserUsed     = errors.New("Authentication has been used by some users")
 )
 
-var LoginTypes = map[int]string{
-	LT_LDAP: "LDAP",
-	LT_SMTP: "SMTP",
+var LoginTypes = map[LoginType]string{
+	LDAP: "LDAP",
+	SMTP: "SMTP",
 }
 
 // Ensure structs implmented interface.
@@ -49,7 +50,6 @@ type LDAPConfig struct {
 	ldap.Ldapsource
 }
 
-// implement
 func (cfg *LDAPConfig) FromDB(bs []byte) error {
 	return json.Unmarshal(bs, &cfg.Ldapsource)
 }
@@ -65,7 +65,6 @@ type SMTPConfig struct {
 	TLS  bool
 }
 
-// implement
 func (cfg *SMTPConfig) FromDB(bs []byte) error {
 	return json.Unmarshal(bs, cfg)
 }
@@ -76,13 +75,13 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) {
 
 type LoginSource struct {
 	Id                int64
-	Type              int
-	Name              string          `xorm:"unique"`
-	IsActived         bool            `xorm:"not null default false"`
+	Type              LoginType
+	Name              string          `xorm:"UNIQUE"`
+	IsActived         bool            `xorm:"NOT NULL DEFAULT false"`
 	Cfg               core.Conversion `xorm:"TEXT"`
-	Created           time.Time       `xorm:"created"`
-	Updated           time.Time       `xorm:"updated"`
-	AllowAutoRegister bool            `xorm:"not null default false"`
+	AllowAutoRegister bool            `xorm:"NOT NULL DEFAULT false"`
+	Created           time.Time       `xorm:"CREATED"`
+	Updated           time.Time       `xorm:"UPDATED"`
 }
 
 func (source *LoginSource) TypeString() string {
@@ -97,61 +96,59 @@ func (source *LoginSource) SMTP() *SMTPConfig {
 	return source.Cfg.(*SMTPConfig)
 }
 
-// for xorm callback
 func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
 	if colName == "type" {
 		ty := (*val).(int64)
-		switch ty {
-		case LT_LDAP:
+		switch LoginType(ty) {
+		case LDAP:
 			source.Cfg = new(LDAPConfig)
-		case LT_SMTP:
+		case SMTP:
 			source.Cfg = new(SMTPConfig)
 		}
 	}
 }
 
+func CreateSource(source *LoginSource) error {
+	_, err := x.Insert(source)
+	return err
+}
+
 func GetAuths() ([]*LoginSource, error) {
-	var auths = make([]*LoginSource, 0)
-	err := orm.Find(&auths)
+	var auths = make([]*LoginSource, 0, 5)
+	err := x.Find(&auths)
 	return auths, err
 }
 
 func GetLoginSourceById(id int64) (*LoginSource, error) {
 	source := new(LoginSource)
-	has, err := orm.Id(id).Get(source)
+	has, err := x.Id(id).Get(source)
 	if err != nil {
 		return nil, err
-	}
-	if !has {
+	} else if !has {
 		return nil, ErrAuthenticationNotExist
 	}
 	return source, nil
 }
 
-func AddSource(source *LoginSource) error {
-	_, err := orm.Insert(source)
-	return err
-}
-
 func UpdateSource(source *LoginSource) error {
-	_, err := orm.Id(source.Id).AllCols().Update(source)
+	_, err := x.Id(source.Id).AllCols().Update(source)
 	return err
 }
 
 func DelLoginSource(source *LoginSource) error {
-	cnt, err := orm.Count(&User{LoginSource: source.Id})
+	cnt, err := x.Count(&User{LoginSource: source.Id})
 	if err != nil {
 		return err
 	}
 	if cnt > 0 {
 		return ErrAuthenticationUserUsed
 	}
-	_, err = orm.Id(source.Id).Delete(&LoginSource{})
+	_, err = x.Id(source.Id).Delete(&LoginSource{})
 	return err
 }
 
-// login a user
-func LoginUser(uname, passwd string) (*User, error) {
+// UserSignIn validates user name and password.
+func UserSignIn(uname, passwd string) (*User, error) {
 	var u *User
 	if strings.Contains(uname, "@") {
 		u = &User{Email: uname}
@@ -159,19 +156,19 @@ func LoginUser(uname, passwd string) (*User, error) {
 		u = &User{LowerName: strings.ToLower(uname)}
 	}
 
-	has, err := orm.Get(u)
+	has, err := x.Get(u)
 	if err != nil {
 		return nil, err
 	}
 
-	if u.LoginType == LT_NOTYPE {
+	if u.LoginType == NOTYPE {
 		if has {
-			u.LoginType = LT_PLAIN
+			u.LoginType = PLAIN
 		}
 	}
 
 	// for plain login, user must have existed.
-	if u.LoginType == LT_PLAIN {
+	if u.LoginType == PLAIN {
 		if !has {
 			return nil, ErrUserNotExist
 		}
@@ -185,28 +182,26 @@ func LoginUser(uname, passwd string) (*User, error) {
 	} else {
 		if !has {
 			var sources []LoginSource
-			if err = orm.UseBool().Find(&sources,
+			if err = x.UseBool().Find(&sources,
 				&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
 				return nil, err
 			}
 
 			for _, source := range sources {
-				if source.Type == LT_LDAP {
+				if source.Type == LDAP {
 					u, err := LoginUserLdapSource(nil, uname, passwd,
 						source.Id, source.Cfg.(*LDAPConfig), true)
 					if err == nil {
 						return u, nil
-					} else {
-						log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
 					}
-				} else if source.Type == LT_SMTP {
+					log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
+				} else if source.Type == SMTP {
 					u, err := LoginUserSMTPSource(nil, uname, passwd,
 						source.Id, source.Cfg.(*SMTPConfig), true)
 					if err == nil {
 						return u, nil
-					} else {
-						log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
 					}
+					log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
 				}
 			}
 
@@ -214,7 +209,7 @@ func LoginUser(uname, passwd string) (*User, error) {
 		}
 
 		var source LoginSource
-		hasSource, err := orm.Id(u.LoginSource).Get(&source)
+		hasSource, err := x.Id(u.LoginSource).Get(&source)
 		if err != nil {
 			return nil, err
 		} else if !hasSource {
@@ -224,10 +219,10 @@ func LoginUser(uname, passwd string) (*User, error) {
 		}
 
 		switch u.LoginType {
-		case LT_LDAP:
+		case LDAP:
 			return LoginUserLdapSource(u, u.LoginName, passwd,
 				source.Id, source.Cfg.(*LDAPConfig), false)
-		case LT_SMTP:
+		case SMTP:
 			return LoginUserSMTPSource(u, u.LoginName, passwd,
 				source.Id, source.Cfg.(*SMTPConfig), false)
 		}
@@ -252,7 +247,7 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L
 	user = &User{
 		LowerName:   strings.ToLower(name),
 		Name:        strings.ToLower(name),
-		LoginType:   LT_LDAP,
+		LoginType:   LDAP,
 		LoginSource: sourceId,
 		LoginName:   name,
 		IsActive:    true,
@@ -260,7 +255,7 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L
 		Email:       mail,
 	}
 
-	return RegisterUser(user)
+	return CreateUser(user)
 }
 
 type loginAuth struct {
@@ -320,9 +315,8 @@ func SmtpAuth(host string, port int, a smtp.Auth, useTls bool) error {
 			return err
 		}
 		return nil
-	} else {
-		return ErrUnsupportedLoginType
 	}
+	return ErrUnsupportedLoginType
 }
 
 // Query if name/passwd can login against the LDAP direcotry pool
@@ -358,13 +352,12 @@ func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *S
 	user = &User{
 		LowerName:   strings.ToLower(loginName),
 		Name:        strings.ToLower(loginName),
-		LoginType:   LT_SMTP,
+		LoginType:   SMTP,
 		LoginSource: sourceId,
 		LoginName:   name,
 		IsActive:    true,
 		Passwd:      passwd,
 		Email:       name,
 	}
-
-	return RegisterUser(user)
+	return CreateUser(user)
 }

+ 28 - 27
models/models.go

@@ -18,7 +18,7 @@ import (
 )
 
 var (
-	orm    *xorm.Engine
+	x      *xorm.Engine
 	tables []interface{}
 
 	HasEngine bool
@@ -35,7 +35,7 @@ func init() {
 	tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
 		new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
 		new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
-		new(Milestone), new(Label))
+		new(Milestone), new(Label), new(HookTask), new(Team), new(OrgUser), new(TeamUser))
 }
 
 func LoadModelsConfig() {
@@ -46,7 +46,9 @@ func LoadModelsConfig() {
 	DbCfg.Host = setting.Cfg.MustValue("database", "HOST")
 	DbCfg.Name = setting.Cfg.MustValue("database", "NAME")
 	DbCfg.User = setting.Cfg.MustValue("database", "USER")
-	DbCfg.Pwd = setting.Cfg.MustValue("database", "PASSWD")
+	if len(DbCfg.Pwd) == 0 {
+		DbCfg.Pwd = setting.Cfg.MustValue("database", "PASSWD")
+	}
 	DbCfg.SslMode = setting.Cfg.MustValue("database", "SSL_MODE")
 	DbCfg.Path = setting.Cfg.MustValue("database", "PATH", "data/gogs.db")
 }
@@ -67,7 +69,6 @@ func NewTestEngine(x *xorm.Engine) (err error) {
 		}
 		cnnstr := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
 			DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode)
-		//fmt.Println(cnnstr)
 		x, err = xorm.NewEngine("postgres", cnnstr)
 	case "sqlite3":
 		if !EnableSQLite3 {
@@ -87,7 +88,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
 func SetEngine() (err error) {
 	switch DbCfg.Type {
 	case "mysql":
-		orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
+		x, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
 			DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name))
 	case "postgres":
 		var host, port = "127.0.0.1", "5432"
@@ -98,11 +99,11 @@ func SetEngine() (err error) {
 		if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
 			port = fields[1]
 		}
-		orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
+		x, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
 			DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode))
 	case "sqlite3":
 		os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
-		orm, err = xorm.NewEngine("sqlite3", DbCfg.Path)
+		x, err = xorm.NewEngine("sqlite3", DbCfg.Path)
 	default:
 		return fmt.Errorf("Unknown database type: %s", DbCfg.Type)
 	}
@@ -119,11 +120,11 @@ func SetEngine() (err error) {
 	if err != nil {
 		return fmt.Errorf("models.init(fail to create xorm.log): %v", err)
 	}
-	orm.Logger = xorm.NewSimpleLogger(f)
+	x.Logger = xorm.NewSimpleLogger(f)
 
-	orm.ShowSQL = true
-	orm.ShowDebug = true
-	orm.ShowErr = true
+	x.ShowSQL = true
+	x.ShowDebug = true
+	x.ShowErr = true
 	return nil
 }
 
@@ -131,7 +132,7 @@ func NewEngine() (err error) {
 	if err = SetEngine(); err != nil {
 		return err
 	}
-	if err = orm.Sync(tables...); err != nil {
+	if err = x.Sync2(tables...); err != nil {
 		return fmt.Errorf("sync database struct error: %v\n", err)
 	}
 	return nil
@@ -146,24 +147,24 @@ type Statistic struct {
 }
 
 func GetStatistic() (stats Statistic) {
-	stats.Counter.User, _ = orm.Count(new(User))
-	stats.Counter.PublicKey, _ = orm.Count(new(PublicKey))
-	stats.Counter.Repo, _ = orm.Count(new(Repository))
-	stats.Counter.Watch, _ = orm.Count(new(Watch))
-	stats.Counter.Action, _ = orm.Count(new(Action))
-	stats.Counter.Access, _ = orm.Count(new(Access))
-	stats.Counter.Issue, _ = orm.Count(new(Issue))
-	stats.Counter.Comment, _ = orm.Count(new(Comment))
-	stats.Counter.Mirror, _ = orm.Count(new(Mirror))
-	stats.Counter.Oauth, _ = orm.Count(new(Oauth2))
-	stats.Counter.Release, _ = orm.Count(new(Release))
-	stats.Counter.LoginSource, _ = orm.Count(new(LoginSource))
-	stats.Counter.Webhook, _ = orm.Count(new(Webhook))
-	stats.Counter.Milestone, _ = orm.Count(new(Milestone))
+	stats.Counter.User, _ = x.Count(new(User))
+	stats.Counter.PublicKey, _ = x.Count(new(PublicKey))
+	stats.Counter.Repo, _ = x.Count(new(Repository))
+	stats.Counter.Watch, _ = x.Count(new(Watch))
+	stats.Counter.Action, _ = x.Count(new(Action))
+	stats.Counter.Access, _ = x.Count(new(Access))
+	stats.Counter.Issue, _ = x.Count(new(Issue))
+	stats.Counter.Comment, _ = x.Count(new(Comment))
+	stats.Counter.Mirror, _ = x.Count(new(Mirror))
+	stats.Counter.Oauth, _ = x.Count(new(Oauth2))
+	stats.Counter.Release, _ = x.Count(new(Release))
+	stats.Counter.LoginSource, _ = x.Count(new(LoginSource))
+	stats.Counter.Webhook, _ = x.Count(new(Webhook))
+	stats.Counter.Milestone, _ = x.Count(new(Milestone))
 	return
 }
 
 // DumpDatabase dumps all data from database to file system.
 func DumpDatabase(filePath string) error {
-	return orm.DumpAllToFile(filePath)
+	return x.DumpAllToFile(filePath)
 }

+ 16 - 16
models/oauth2.go

@@ -8,16 +8,16 @@ import (
 	"errors"
 )
 
-// OT: Oauth2 Type
+type OauthType int
+
 const (
-	OT_GITHUB = iota + 1
-	OT_GOOGLE
-	OT_TWITTER
-	OT_QQ
-	OT_WEIBO
-	OT_BITBUCKET
-	OT_OSCHINA
-	OT_FACEBOOK
+	GITHUB OauthType = iota + 1
+	GOOGLE
+	TWITTER
+	QQ
+	WEIBO
+	BITBUCKET
+	FACEBOOK
 )
 
 var (
@@ -35,18 +35,18 @@ type Oauth2 struct {
 }
 
 func BindUserOauth2(userId, oauthId int64) error {
-	_, err := orm.Id(oauthId).Update(&Oauth2{Uid: userId})
+	_, err := x.Id(oauthId).Update(&Oauth2{Uid: userId})
 	return err
 }
 
 func AddOauth2(oa *Oauth2) error {
-	_, err := orm.Insert(oa)
+	_, err := x.Insert(oa)
 	return err
 }
 
 func GetOauth2(identity string) (oa *Oauth2, err error) {
 	oa = &Oauth2{Identity: identity}
-	isExist, err := orm.Get(oa)
+	isExist, err := x.Get(oa)
 	if err != nil {
 		return
 	} else if !isExist {
@@ -60,7 +60,7 @@ func GetOauth2(identity string) (oa *Oauth2, err error) {
 
 func GetOauth2ById(id int64) (oa *Oauth2, err error) {
 	oa = new(Oauth2)
-	has, err := orm.Id(id).Get(oa)
+	has, err := x.Id(id).Get(oa)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -71,18 +71,18 @@ func GetOauth2ById(id int64) (oa *Oauth2, err error) {
 
 // GetOauthByUserId returns list of oauthes that are releated to given user.
 func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) {
-	err = orm.Find(&oas, Oauth2{Uid: uid})
+	err = x.Find(&oas, Oauth2{Uid: uid})
 	return oas, err
 }
 
 // DeleteOauth2ById deletes a oauth2 by ID.
 func DeleteOauth2ById(id int64) error {
-	_, err := orm.Delete(&Oauth2{Id: id})
+	_, err := x.Delete(&Oauth2{Id: id})
 	return err
 }
 
 // CleanUnbindOauth deletes all unbind OAuthes.
 func CleanUnbindOauth() error {
-	_, err := orm.Delete(&Oauth2{Uid: -1})
+	_, err := x.Delete(&Oauth2{Uid: -1})
 	return err
 }

+ 236 - 0
models/org.go

@@ -0,0 +1,236 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+	"strings"
+
+	"github.com/gogits/gogs/modules/base"
+)
+
+// GetOwnerTeam returns owner team of organization.
+func (org *User) GetOwnerTeam() (*Team, error) {
+	t := &Team{
+		OrgId: org.Id,
+		Name:  OWNER_TEAM,
+	}
+	_, err := x.Get(t)
+	return t, err
+}
+
+// CreateOrganization creates record of a new organization.
+func CreateOrganization(org, owner *User) (*User, error) {
+	if !IsLegalName(org.Name) {
+		return nil, ErrUserNameIllegal
+	}
+
+	isExist, err := IsUserExist(org.Name)
+	if err != nil {
+		return nil, err
+	} else if isExist {
+		return nil, ErrUserAlreadyExist
+	}
+
+	isExist, err = IsEmailUsed(org.Email)
+	if err != nil {
+		return nil, err
+	} else if isExist {
+		return nil, ErrEmailAlreadyUsed
+	}
+
+	org.LowerName = strings.ToLower(org.Name)
+	org.FullName = org.Name
+	org.Avatar = base.EncodeMd5(org.Email)
+	org.AvatarEmail = org.Email
+	// No password for organization.
+	org.NumTeams = 1
+	org.NumMembers = 1
+
+	sess := x.NewSession()
+	defer sess.Close()
+	if err = sess.Begin(); err != nil {
+		return nil, err
+	}
+
+	if _, err = sess.Insert(org); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	// Create default owner team.
+	t := &Team{
+		OrgId:      org.Id,
+		Name:       OWNER_TEAM,
+		Authorize:  ORG_ADMIN,
+		NumMembers: 1,
+	}
+	if _, err = sess.Insert(t); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	// Add initial creator to organization and owner team.
+	ou := &OrgUser{
+		Uid:     owner.Id,
+		OrgId:   org.Id,
+		IsOwner: true,
+		NumTeam: 1,
+	}
+	if _, err = sess.Insert(ou); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	tu := &TeamUser{
+		Uid:    owner.Id,
+		OrgId:  org.Id,
+		TeamId: t.Id,
+	}
+	if _, err = sess.Insert(tu); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	return org, sess.Commit()
+}
+
+// TODO: need some kind of mechanism to record failure.
+// DeleteOrganization completely and permanently deletes everything of organization.
+func DeleteOrganization(org *User) (err error) {
+	if err := DeleteUser(org); err != nil {
+		return err
+	}
+
+	sess := x.NewSession()
+	defer sess.Close()
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if _, err = sess.Delete(&Team{OrgId: org.Id}); err != nil {
+		sess.Rollback()
+		return err
+	}
+	if _, err = sess.Delete(&OrgUser{OrgId: org.Id}); err != nil {
+		sess.Rollback()
+		return err
+	}
+	if _, err = sess.Delete(&TeamUser{OrgId: org.Id}); err != nil {
+		sess.Rollback()
+		return err
+	}
+	return sess.Commit()
+}
+
+type AuthorizeType int
+
+const (
+	ORG_READABLE AuthorizeType = iota + 1
+	ORG_WRITABLE
+	ORG_ADMIN
+)
+
+const OWNER_TEAM = "Owner"
+
+// Team represents a organization team.
+type Team struct {
+	Id          int64
+	OrgId       int64 `xorm:"INDEX"`
+	Name        string
+	Description string
+	Authorize   AuthorizeType
+	RepoIds     string `xorm:"TEXT"`
+	NumMembers  int
+	NumRepos    int
+}
+
+// NewTeam creates a record of new team.
+func NewTeam(t *Team) error {
+	_, err := x.Insert(t)
+	return err
+}
+
+func UpdateTeam(t *Team) error {
+	if len(t.Description) > 255 {
+		t.Description = t.Description[:255]
+	}
+
+	_, err := x.Id(t.Id).AllCols().Update(t)
+	return err
+}
+
+// ________                ____ ___
+// \_____  \_______  ____ |    |   \______ ___________
+//  /   |   \_  __ \/ ___\|    |   /  ___// __ \_  __ \
+// /    |    \  | \/ /_/  >    |  /\___ \\  ___/|  | \/
+// \_______  /__|  \___  /|______//____  >\___  >__|
+//         \/     /_____/              \/     \/
+
+// OrgUser represents an organization-user relation.
+type OrgUser struct {
+	Id       int64
+	Uid      int64 `xorm:"INDEX"`
+	OrgId    int64 `xorm:"INDEX"`
+	IsPublic bool
+	IsOwner  bool
+	NumTeam  int
+}
+
+// GetOrgUsersByUserId returns all organization-user relations by user ID.
+func GetOrgUsersByUserId(uid int64) ([]*OrgUser, error) {
+	ous := make([]*OrgUser, 0, 10)
+	err := x.Where("uid=?", uid).Find(&ous)
+	return ous, err
+}
+
+// GetOrgUsersByOrgId returns all organization-user relations by organization ID.
+func GetOrgUsersByOrgId(orgId int64) ([]*OrgUser, error) {
+	ous := make([]*OrgUser, 0, 10)
+	err := x.Where("org_id=?", orgId).Find(&ous)
+	return ous, err
+}
+
+func GetOrganizationCount(u *User) (int64, error) {
+	return x.Where("uid=?", u.Id).Count(new(OrgUser))
+}
+
+// IsOrganizationOwner returns true if given user ID is in the owner team.
+func IsOrganizationOwner(orgId, uid int64) bool {
+	has, _ := x.Where("is_owner=?", true).Get(&OrgUser{Uid: uid, OrgId: orgId})
+	return has
+}
+
+// ___________                    ____ ___
+// \__    ___/___ _____    _____ |    |   \______ ___________
+//   |    |_/ __ \\__  \  /     \|    |   /  ___// __ \_  __ \
+//   |    |\  ___/ / __ \|  Y Y  \    |  /\___ \\  ___/|  | \/
+//   |____| \___  >____  /__|_|  /______//____  >\___  >__|
+//              \/     \/      \/             \/     \/
+
+// TeamUser represents an team-user relation.
+type TeamUser struct {
+	Id     int64
+	Uid    int64
+	OrgId  int64 `xorm:"INDEX"`
+	TeamId int64
+}
+
+// GetTeamMembers returns all members in given team of organization.
+func GetTeamMembers(orgId, teamId int64) ([]*User, error) {
+	tus := make([]*TeamUser, 0, 10)
+	err := x.Where("org_id=?", orgId).And("team_id=?", teamId).Find(&tus)
+	if err != nil {
+		return nil, err
+	}
+
+	us := make([]*User, len(tus))
+	for i, tu := range tus {
+		us[i], err = GetUserById(tu.Uid)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return us, nil
+}

+ 18 - 18
models/publickey.go

@@ -19,9 +19,9 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
-	qlog "github.com/qiniu/log"
 
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/process"
 )
 
 const (
@@ -37,7 +37,7 @@ var (
 var sshOpLocker = sync.Mutex{}
 
 var (
-	sshPath string // SSH directory.
+	SshPath string // SSH directory.
 	appPath string // Execution(binary) path.
 )
 
@@ -54,7 +54,7 @@ func exePath() (string, error) {
 func homeDir() string {
 	home, err := com.HomeDir()
 	if err != nil {
-		qlog.Fatalln(err)
+		log.Fatal("Fail to get home directory: %v", err)
 	}
 	return home
 }
@@ -63,13 +63,13 @@ func init() {
 	var err error
 
 	if appPath, err = exePath(); err != nil {
-		qlog.Fatalf("publickey.init(fail to get app path): %v\n", err)
+		log.Fatal("publickey.init(fail to get app path): %v\n", err)
 	}
 
 	// Determine and create .ssh path.
-	sshPath = filepath.Join(homeDir(), ".ssh")
-	if err = os.MkdirAll(sshPath, os.ModePerm); err != nil {
-		qlog.Fatalf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err)
+	SshPath = filepath.Join(homeDir(), ".ssh")
+	if err = os.MkdirAll(SshPath, os.ModePerm); err != nil {
+		log.Fatal("publickey.init(fail to create SshPath(%s)): %v\n", SshPath, err)
 	}
 }
 
@@ -94,7 +94,7 @@ func saveAuthorizedKeyFile(key *PublicKey) error {
 	sshOpLocker.Lock()
 	defer sshOpLocker.Unlock()
 
-	fpath := filepath.Join(sshPath, "authorized_keys")
+	fpath := filepath.Join(SshPath, "authorized_keys")
 	f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
 	if err != nil {
 		return err
@@ -107,7 +107,7 @@ func saveAuthorizedKeyFile(key *PublicKey) error {
 
 // AddPublicKey adds new public key to database and authorized_keys file.
 func AddPublicKey(key *PublicKey) (err error) {
-	has, err := orm.Get(key)
+	has, err := x.Get(key)
 	if err != nil {
 		return err
 	} else if has {
@@ -121,7 +121,7 @@ func AddPublicKey(key *PublicKey) (err error) {
 	if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
 		return err
 	}
-	stdout, stderr, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath)
+	stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
 	if err != nil {
 		return errors.New("ssh-keygen -l -f: " + stderr)
 	} else if len(stdout) < 2 {
@@ -130,11 +130,11 @@ func AddPublicKey(key *PublicKey) (err error) {
 	key.Fingerprint = strings.Split(stdout, " ")[1]
 
 	// Save SSH key.
-	if _, err = orm.Insert(key); err != nil {
+	if _, err = x.Insert(key); err != nil {
 		return err
 	} else if err = saveAuthorizedKeyFile(key); err != nil {
 		// Roll back.
-		if _, err2 := orm.Delete(key); err2 != nil {
+		if _, err2 := x.Delete(key); err2 != nil {
 			return err2
 		}
 		return err
@@ -146,7 +146,7 @@ func AddPublicKey(key *PublicKey) (err error) {
 // ListPublicKey returns a list of all public keys that user has.
 func ListPublicKey(uid int64) ([]PublicKey, error) {
 	keys := make([]PublicKey, 0, 5)
-	err := orm.Find(&keys, &PublicKey{OwnerId: uid})
+	err := x.Find(&keys, &PublicKey{OwnerId: uid})
 	return keys, err
 }
 
@@ -161,7 +161,7 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
 	}
 	defer fr.Close()
 
-	fw, err := os.Create(tmpP)
+	fw, err := os.OpenFile(tmpP, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
 	if err != nil {
 		return err
 	}
@@ -205,19 +205,19 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
 
 // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
 func DeletePublicKey(key *PublicKey) error {
-	has, err := orm.Get(key)
+	has, err := x.Get(key)
 	if err != nil {
 		return err
 	} else if !has {
 		return ErrKeyNotExist
 	}
 
-	if _, err = orm.Delete(key); err != nil {
+	if _, err = x.Delete(key); err != nil {
 		return err
 	}
 
-	fpath := filepath.Join(sshPath, "authorized_keys")
-	tmpPath := filepath.Join(sshPath, "authorized_keys.tmp")
+	fpath := filepath.Join(SshPath, "authorized_keys")
+	tmpPath := filepath.Join(SshPath, "authorized_keys.tmp")
 	log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath)
 
 	if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {

+ 92 - 29
models/release.go

@@ -6,15 +6,16 @@ package models
 
 import (
 	"errors"
+	"sort"
 	"strings"
 	"time"
 
-	"github.com/Unknwon/com"
 	"github.com/gogits/git"
 )
 
 var (
 	ErrReleaseAlreadyExist = errors.New("Release already exist")
+	ErrReleaseNotExist     = errors.New("Release does not exist")
 )
 
 // Release represents a release of repository.
@@ -23,21 +24,17 @@ type Release struct {
 	RepoId           int64
 	PublisherId      int64
 	Publisher        *User `xorm:"-"`
-	Title            string
 	TagName          string
 	LowerTagName     string
-	SHA1             string
+	Target           string
+	Title            string
+	Sha1             string `xorm:"VARCHAR(40)"`
 	NumCommits       int
 	NumCommitsBehind int    `xorm:"-"`
 	Note             string `xorm:"TEXT"`
+	IsDraft          bool   `xorm:"NOT NULL DEFAULT false"`
 	IsPrerelease     bool
-	Created          time.Time `xorm:"created"`
-}
-
-// GetReleasesByRepoId returns a list of releases of repository.
-func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
-	err = orm.Desc("created").Find(&rels, Release{RepoId: repoId})
-	return rels, err
+	Created          time.Time `xorm:"CREATED"`
 }
 
 // IsReleaseExist returns true if release with given tag name already exists.
@@ -46,7 +43,34 @@ func IsReleaseExist(repoId int64, tagName string) (bool, error) {
 		return false, nil
 	}
 
-	return orm.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
+	return x.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
+}
+
+func createTag(gitRepo *git.Repository, rel *Release) error {
+	// Only actual create when publish.
+	if !rel.IsDraft {
+		if !gitRepo.IsTagExist(rel.TagName) {
+			commit, err := gitRepo.GetCommitOfBranch(rel.Target)
+			if err != nil {
+				return err
+			}
+
+			if err = gitRepo.CreateTag(rel.TagName, commit.Id.String()); err != nil {
+				return err
+			}
+		} else {
+			commit, err := gitRepo.GetCommitOfTag(rel.TagName)
+			if err != nil {
+				return err
+			}
+
+			rel.NumCommits, err = commit.CommitsCount()
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
 }
 
 // CreateRelease creates a new release of repository.
@@ -58,26 +82,65 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
 		return ErrReleaseAlreadyExist
 	}
 
-	if !gitRepo.IsTagExist(rel.TagName) {
-		_, stderr, err := com.ExecCmdDir(gitRepo.Path, "git", "tag", rel.TagName, "-m", rel.Title)
-		if err != nil {
-			return err
-		} else if strings.Contains(stderr, "fatal:") {
-			return errors.New(stderr)
-		}
-	} else {
-		commit, err := gitRepo.GetCommitOfTag(rel.TagName)
-		if err != nil {
-			return err
-		}
+	if err = createTag(gitRepo, rel); err != nil {
+		return err
+	}
+	rel.LowerTagName = strings.ToLower(rel.TagName)
+	_, err = x.InsertOne(rel)
+	return err
+}
 
-		rel.NumCommits, err = commit.CommitsCount()
-		if err != nil {
-			return err
-		}
+// GetRelease returns release by given ID.
+func GetRelease(repoId int64, tagName string) (*Release, error) {
+	isExist, err := IsReleaseExist(repoId, tagName)
+	if err != nil {
+		return nil, err
+	} else if !isExist {
+		return nil, ErrReleaseNotExist
 	}
 
-	rel.LowerTagName = strings.ToLower(rel.TagName)
-	_, err = orm.InsertOne(rel)
+	rel := &Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}
+	_, err = x.Get(rel)
+	return rel, err
+}
+
+// GetReleasesByRepoId returns a list of releases of repository.
+func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
+	err = x.Desc("created").Find(&rels, Release{RepoId: repoId})
+	return rels, err
+}
+
+type ReleaseSorter struct {
+	rels []*Release
+}
+
+func (rs *ReleaseSorter) Len() int {
+	return len(rs.rels)
+}
+
+func (rs *ReleaseSorter) Less(i, j int) bool {
+	diffNum := rs.rels[i].NumCommits - rs.rels[j].NumCommits
+	if diffNum != 0 {
+		return diffNum > 0
+	}
+	return rs.rels[i].Created.After(rs.rels[j].Created)
+}
+
+func (rs *ReleaseSorter) Swap(i, j int) {
+	rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
+}
+
+// SortReleases sorts releases by number of commits and created time.
+func SortReleases(rels []*Release) {
+	sorter := &ReleaseSorter{rels: rels}
+	sort.Sort(sorter)
+}
+
+// UpdateRelease updates information of a release.
+func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
+	if err = createTag(gitRepo, rel); err != nil {
+		return err
+	}
+	_, err = x.Id(rel.Id).AllCols().Update(rel)
 	return err
 }

+ 273 - 199
models/repo.go

@@ -9,9 +9,9 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
+	"sort"
 	"strings"
 	"time"
 	"unicode/utf8"
@@ -24,9 +24,14 @@ import (
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/bin"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/process"
 	"github.com/gogits/gogs/modules/setting"
 )
 
+const (
+	TPL_UPDATE_HOOK = "#!/usr/bin/env %s\n%s update $1 $2 $3\n"
+)
+
 var (
 	ErrRepoAlreadyExist  = errors.New("Repository already exist")
 	ErrRepoNotExist      = errors.New("Repository does not exist")
@@ -75,19 +80,21 @@ func LoadRepoConfig() {
 
 	LanguageIgns = typeFiles[0]
 	Licenses = typeFiles[1]
+	sort.Strings(LanguageIgns)
+	sort.Strings(Licenses)
 }
 
 func NewRepoContext() {
 	zip.Verbose = false
 
 	// Check if server has basic git setting.
-	stdout, stderr, err := com.ExecCmd("git", "config", "--get", "user.name")
+	stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name")
 	if strings.Contains(stderr, "fatal:") {
 		log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr)
 	} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
-		if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
+		if _, stderr, err = process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
 			log.Fatal("repo.NewRepoContext(fail to set git user.email): %s", stderr)
-		} else if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
+		} else if _, stderr, err = process.Exec("NewRepoContext(set name)", "git", "config", "--global", "user.name", "Gogs"); err != nil {
 			log.Fatal("repo.NewRepoContext(fail to set git user.name): %s", stderr)
 		}
 	}
@@ -106,11 +113,11 @@ func NewRepoContext() {
 // Repository represents a git repository.
 type Repository struct {
 	Id                  int64
-	OwnerId             int64 `xorm:"unique(s)"`
+	OwnerId             int64 `xorm:"UNIQUE(s)"`
 	Owner               *User `xorm:"-"`
 	ForkId              int64
-	LowerName           string `xorm:"unique(s) index not null"`
-	Name                string `xorm:"index not null"`
+	LowerName           string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	Name                string `xorm:"INDEX NOT NULL"`
 	Description         string
 	Website             string
 	NumWatches          int
@@ -128,8 +135,8 @@ type Repository struct {
 	IsBare              bool
 	IsGoget             bool
 	DefaultBranch       string
-	Created             time.Time `xorm:"created"`
-	Updated             time.Time `xorm:"updated"`
+	Created             time.Time `xorm:"CREATED"`
+	Updated             time.Time `xorm:"UPDATED"`
 }
 
 func (repo *Repository) GetOwner() (err error) {
@@ -140,7 +147,7 @@ func (repo *Repository) GetOwner() (err error) {
 // IsRepositoryExist returns true if the repository with given name under user has already existed.
 func IsRepositoryExist(u *User, repoName string) (bool, error) {
 	repo := Repository{OwnerId: u.Id}
-	has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
+	has, err := x.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
 	if err != nil {
 		return has, err
 	} else if !has {
@@ -151,7 +158,7 @@ func IsRepositoryExist(u *User, repoName string) (bool, error) {
 }
 
 var (
-	illegalEquals  = []string{"raw", "install", "api", "avatar", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"}
+	illegalEquals  = []string{"raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"}
 	illegalSuffixs = []string{".git"}
 )
 
@@ -181,9 +188,30 @@ type Mirror struct {
 	NextUpdate time.Time
 }
 
+// MirrorRepository creates a mirror repository from source.
+func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
+	// TODO: need timeout.
+	_, stderr, err := process.Exec(fmt.Sprintf("MirrorRepository: %s/%s", userName, repoName),
+		"git", "clone", "--mirror", url, repoPath)
+	if err != nil {
+		return errors.New("git clone --mirror: " + stderr)
+	}
+
+	if _, err = x.InsertOne(&Mirror{
+		RepoId:     repoId,
+		RepoName:   strings.ToLower(userName + "/" + repoName),
+		Interval:   24,
+		NextUpdate: time.Now().Add(24 * time.Hour),
+	}); err != nil {
+		return err
+	}
+
+	return git.UnpackRefs(repoPath)
+}
+
 func GetMirror(repoId int64) (*Mirror, error) {
 	m := &Mirror{RepoId: repoId}
-	has, err := orm.Get(m)
+	has, err := x.Get(m)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -193,24 +221,26 @@ func GetMirror(repoId int64) (*Mirror, error) {
 }
 
 func UpdateMirror(m *Mirror) error {
-	_, err := orm.Id(m.Id).Update(m)
+	_, err := x.Id(m.Id).Update(m)
 	return err
 }
 
 // MirrorUpdate checks and updates mirror repositories.
 func MirrorUpdate() {
-	if err := orm.Iterate(new(Mirror), func(idx int, bean interface{}) error {
+	if err := x.Iterate(new(Mirror), func(idx int, bean interface{}) error {
 		m := bean.(*Mirror)
 		if m.NextUpdate.After(time.Now()) {
 			return nil
 		}
 
+		// TODO: need timeout.
 		repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
-		_, stderr, err := com.ExecCmdDir(repoPath, "git", "remote", "update")
-		if err != nil {
+		if _, stderr, err := process.ExecDir(
+			repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
+			"git", "remote", "update"); err != nil {
 			return errors.New("git remote update: " + stderr)
 		} else if err = git.UnpackRefs(repoPath); err != nil {
-			return err
+			return errors.New("UnpackRefs: " + err.Error())
 		}
 
 		m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
@@ -220,28 +250,9 @@ func MirrorUpdate() {
 	}
 }
 
-// MirrorRepository creates a mirror repository from source.
-func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
-	_, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath)
-	if err != nil {
-		return errors.New("git clone --mirror: " + stderr)
-	}
-
-	if _, err = orm.InsertOne(&Mirror{
-		RepoId:     repoId,
-		RepoName:   strings.ToLower(userName + "/" + repoName),
-		Interval:   24,
-		NextUpdate: time.Now().Add(24 * time.Hour),
-	}); err != nil {
-		return err
-	}
-
-	return git.UnpackRefs(repoPath)
-}
-
 // MigrateRepository migrates a existing repository from other project hosting.
-func MigrateRepository(user *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
-	repo, err := CreateRepository(user, name, desc, "", "", private, mirror, false)
+func MigrateRepository(u *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
+	repo, err := CreateRepository(u, name, desc, "", "", private, mirror, false)
 	if err != nil {
 		return nil, err
 	}
@@ -250,144 +261,45 @@ func MigrateRepository(user *User, name, desc string, private, mirror bool, url
 	tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
 	os.MkdirAll(tmpDir, os.ModePerm)
 
-	repoPath := RepoPath(user.Name, name)
+	repoPath := RepoPath(u.Name, name)
 
 	repo.IsBare = false
 	if mirror {
-		if err = MirrorRepository(repo.Id, user.Name, repo.Name, repoPath, url); err != nil {
+		if err = MirrorRepository(repo.Id, u.Name, repo.Name, repoPath, url); err != nil {
 			return repo, err
 		}
 		repo.IsMirror = true
 		return repo, UpdateRepository(repo)
 	}
 
+	// TODO: need timeout.
 	// Clone from local repository.
-	_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
+	_, stderr, err := process.Exec(
+		fmt.Sprintf("MigrateRepository(git clone): %s", repoPath),
+		"git", "clone", repoPath, tmpDir)
 	if err != nil {
 		return repo, errors.New("git clone: " + stderr)
 	}
 
+	// TODO: need timeout.
 	// Pull data from source.
-	_, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url)
-	if err != nil {
+	if _, stderr, err = process.ExecDir(
+		tmpDir, fmt.Sprintf("MigrateRepository(git pull): %s", repoPath),
+		"git", "pull", url); err != nil {
 		return repo, errors.New("git pull: " + stderr)
 	}
 
+	// TODO: need timeout.
 	// Push data to local repository.
-	if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil {
+	if _, stderr, err = process.ExecDir(
+		tmpDir, fmt.Sprintf("MigrateRepository(git push): %s", repoPath),
+		"git", "push", "origin", "master"); err != nil {
 		return repo, errors.New("git push: " + stderr)
 	}
 
 	return repo, UpdateRepository(repo)
 }
 
-// CreateRepository creates a repository for given user or orgnaziation.
-func CreateRepository(user *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) {
-	if !IsLegalName(name) {
-		return nil, ErrRepoNameIllegal
-	}
-
-	isExist, err := IsRepositoryExist(user, name)
-	if err != nil {
-		return nil, err
-	} else if isExist {
-		return nil, ErrRepoAlreadyExist
-	}
-
-	repo := &Repository{
-		OwnerId:     user.Id,
-		Name:        name,
-		LowerName:   strings.ToLower(name),
-		Description: desc,
-		IsPrivate:   private,
-		IsBare:      lang == "" && license == "" && !initReadme,
-	}
-	if !repo.IsBare {
-		repo.DefaultBranch = "master"
-	}
-
-	repoPath := RepoPath(user.Name, repo.Name)
-
-	sess := orm.NewSession()
-	defer sess.Close()
-	sess.Begin()
-
-	if _, err = sess.Insert(repo); err != nil {
-		if err2 := os.RemoveAll(repoPath); err2 != nil {
-			log.Error("repo.CreateRepository(repo): %v", err)
-			return nil, errors.New(fmt.Sprintf(
-				"delete repo directory %s/%s failed(1): %v", user.Name, repo.Name, err2))
-		}
-		sess.Rollback()
-		return nil, err
-	}
-
-	mode := AU_WRITABLE
-	if mirror {
-		mode = AU_READABLE
-	}
-	access := Access{
-		UserName: user.LowerName,
-		RepoName: strings.ToLower(path.Join(user.Name, repo.Name)),
-		Mode:     mode,
-	}
-	if _, err = sess.Insert(&access); err != nil {
-		sess.Rollback()
-		if err2 := os.RemoveAll(repoPath); err2 != nil {
-			log.Error("repo.CreateRepository(access): %v", err)
-			return nil, errors.New(fmt.Sprintf(
-				"delete repo directory %s/%s failed(2): %v", user.Name, repo.Name, err2))
-		}
-		return nil, err
-	}
-
-	rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
-	if _, err = sess.Exec(rawSql, user.Id); err != nil {
-		sess.Rollback()
-		if err2 := os.RemoveAll(repoPath); err2 != nil {
-			log.Error("repo.CreateRepository(repo count): %v", err)
-			return nil, errors.New(fmt.Sprintf(
-				"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2))
-		}
-		return nil, err
-	}
-
-	if err = sess.Commit(); err != nil {
-		sess.Rollback()
-		if err2 := os.RemoveAll(repoPath); err2 != nil {
-			log.Error("repo.CreateRepository(commit): %v", err)
-			return nil, errors.New(fmt.Sprintf(
-				"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2))
-		}
-		return nil, err
-	}
-
-	if err = WatchRepo(user.Id, repo.Id, true); err != nil {
-		log.Error("repo.CreateRepository(WatchRepo): %v", err)
-	}
-
-	if err = NewRepoAction(user, repo); err != nil {
-		log.Error("repo.CreateRepository(NewRepoAction): %v", err)
-	}
-
-	// No need for init for mirror.
-	if mirror {
-		return repo, nil
-	}
-
-	if err = initRepository(repoPath, user, repo, initReadme, lang, license); err != nil {
-		return nil, err
-	}
-
-	c := exec.Command("git", "update-server-info")
-	c.Dir = repoPath
-	if err = c.Run(); err != nil {
-		log.Error("repo.CreateRepository(exec update-server-info): %v", err)
-	}
-
-	return repo, nil
-}
-
 // extractGitBareZip extracts git-bare.zip to repository path.
 func extractGitBareZip(repoPath string) error {
 	z, err := zip.Open(filepath.Join(setting.RepoRootPath, "git-bare.zip"))
@@ -402,15 +314,22 @@ func extractGitBareZip(repoPath string) error {
 // initRepoCommit temporarily changes with work directory.
 func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 	var stderr string
-	if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "add", "--all"); err != nil {
+	if _, stderr, err = process.ExecDir(
+		tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath),
+		"git", "add", "--all"); err != nil {
 		return errors.New("git add: " + stderr)
 	}
-	if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
+
+	if _, stderr, err = process.ExecDir(
+		tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath),
+		"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
 		"-m", "Init commit"); err != nil {
 		return errors.New("git commit: " + stderr)
 	}
 
-	if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil {
+	if _, stderr, err = process.ExecDir(
+		tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath),
+		"git", "push", "origin", "master"); err != nil {
 		return errors.New("git push: " + stderr)
 	}
 	return nil
@@ -447,7 +366,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	rp := strings.NewReplacer("\\", "/", " ", "\\ ")
 	// hook/post-update
 	if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"),
-		fmt.Sprintf("#!/usr/bin/env %s\n%s update $1 $2 $3\n", setting.ScriptType,
+		fmt.Sprintf(TPL_UPDATE_HOOK, setting.ScriptType,
 			rp.Replace(appPath))); err != nil {
 		return err
 	}
@@ -468,9 +387,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	tmpDir := filepath.Join(os.TempDir(), base.ToStr(time.Now().Nanosecond()))
 	os.MkdirAll(tmpDir, os.ModePerm)
 
-	_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
+	_, stderr, err := process.Exec(
+		fmt.Sprintf("initRepository(git clone): %s", repoPath),
+		"git", "clone", repoPath, tmpDir)
 	if err != nil {
-		return errors.New("git clone: " + stderr)
+		return errors.New("initRepository(git clone): " + stderr)
 	}
 
 	// README
@@ -486,22 +407,40 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	// .gitignore
 	if repoLang != "" {
 		filePath := "conf/gitignore/" + repoLang
-		if com.IsFile(filePath) {
-			if err := com.Copy(filePath,
-				filepath.Join(tmpDir, fileName["gitign"])); err != nil {
+		targetPath := path.Join(tmpDir, fileName["gitign"])
+		data, err := bin.Asset(filePath)
+		if err == nil {
+			if err = ioutil.WriteFile(targetPath, data, os.ModePerm); err != nil {
 				return err
 			}
+		} else {
+			// Check custom files.
+			filePath = path.Join(setting.CustomPath, "conf/gitignore", repoLang)
+			if com.IsFile(filePath) {
+				if err := com.Copy(filePath, targetPath); err != nil {
+					return err
+				}
+			}
 		}
 	}
 
 	// LICENSE
 	if license != "" {
 		filePath := "conf/license/" + license
-		if com.IsFile(filePath) {
-			if err := com.Copy(filePath,
-				filepath.Join(tmpDir, fileName["license"])); err != nil {
+		targetPath := path.Join(tmpDir, fileName["license"])
+		data, err := bin.Asset(filePath)
+		if err == nil {
+			if err = ioutil.WriteFile(targetPath, data, os.ModePerm); err != nil {
 				return err
 			}
+		} else {
+			// Check custom files.
+			filePath = path.Join(setting.CustomPath, "conf/license", license)
+			if com.IsFile(filePath) {
+				if err := com.Copy(filePath, targetPath); err != nil {
+					return err
+				}
+			}
 		}
 	}
 
@@ -515,17 +454,156 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	return initRepoCommit(tmpDir, user.NewGitSig())
 }
 
+// CreateRepository creates a repository for given user or organization.
+func CreateRepository(u *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) {
+	if !IsLegalName(name) {
+		return nil, ErrRepoNameIllegal
+	}
+
+	isExist, err := IsRepositoryExist(u, name)
+	if err != nil {
+		return nil, err
+	} else if isExist {
+		return nil, ErrRepoAlreadyExist
+	}
+
+	sess := x.NewSession()
+	defer sess.Close()
+	if err = sess.Begin(); err != nil {
+		return nil, err
+	}
+
+	repo := &Repository{
+		OwnerId:     u.Id,
+		Owner:       u,
+		Name:        name,
+		LowerName:   strings.ToLower(name),
+		Description: desc,
+		IsPrivate:   private,
+		IsBare:      lang == "" && license == "" && !initReadme,
+	}
+	if !repo.IsBare {
+		repo.DefaultBranch = "master"
+	}
+
+	if _, err = sess.Insert(repo); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	var t *Team // Owner team.
+
+	mode := WRITABLE
+	if mirror {
+		mode = READABLE
+	}
+	access := &Access{
+		UserName: u.LowerName,
+		RepoName: strings.ToLower(path.Join(u.Name, repo.Name)),
+		Mode:     mode,
+	}
+	// Give access to all members in owner team.
+	if u.IsOrganization() {
+		t, err = u.GetOwnerTeam()
+		if err != nil {
+			sess.Rollback()
+			return nil, err
+		}
+		us, err := GetTeamMembers(u.Id, t.Id)
+		if err != nil {
+			sess.Rollback()
+			return nil, err
+		}
+		for _, u := range us {
+			access.UserName = u.LowerName
+			if _, err = sess.Insert(access); err != nil {
+				sess.Rollback()
+				return nil, err
+			}
+		}
+	} else {
+		if _, err = sess.Insert(access); err != nil {
+			sess.Rollback()
+			return nil, err
+		}
+	}
+
+	rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
+	if _, err = sess.Exec(rawSql, u.Id); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	// Update owner team info and count.
+	if u.IsOrganization() {
+		t.RepoIds += "$" + base.ToStr(repo.Id) + "|"
+		t.NumRepos++
+		if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
+			sess.Rollback()
+			return nil, err
+		}
+	}
+
+	if err = sess.Commit(); err != nil {
+		return nil, err
+	}
+
+	if u.IsOrganization() {
+		ous, err := GetOrgUsersByOrgId(u.Id)
+		if err != nil {
+			log.Error("repo.CreateRepository(GetOrgUsersByOrgId): %v", err)
+		} else {
+			for _, ou := range ous {
+				if err = WatchRepo(ou.Uid, repo.Id, true); err != nil {
+					log.Error("repo.CreateRepository(WatchRepo): %v", err)
+				}
+			}
+		}
+	}
+	if err = WatchRepo(u.Id, repo.Id, true); err != nil {
+		log.Error("repo.CreateRepository(WatchRepo2): %v", err)
+	}
+
+	if err = NewRepoAction(u, repo); err != nil {
+		log.Error("repo.CreateRepository(NewRepoAction): %v", err)
+	}
+
+	// No need for init for mirror.
+	if mirror {
+		return repo, nil
+	}
+
+	repoPath := RepoPath(u.Name, repo.Name)
+	if err = initRepository(repoPath, u, repo, initReadme, lang, license); err != nil {
+		if err2 := os.RemoveAll(repoPath); err2 != nil {
+			log.Error("repo.CreateRepository(initRepository): %v", err)
+			return nil, errors.New(fmt.Sprintf(
+				"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2))
+		}
+		return nil, err
+	}
+
+	_, stderr, err := process.ExecDir(
+		repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath),
+		"git", "update-server-info")
+	if err != nil {
+		return nil, errors.New("CreateRepository(git update-server-info): " + stderr)
+	}
+
+	return repo, nil
+}
+
 // GetRepositoriesWithUsers returns given number of repository objects with offset.
 // It also auto-gets corresponding users.
 func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
 	repos := make([]*Repository, 0, num)
-	if err := orm.Limit(num, offset).Asc("id").Find(&repos); err != nil {
+	if err := x.Limit(num, offset).Asc("id").Find(&repos); err != nil {
 		return nil, err
 	}
 
 	for _, repo := range repos {
 		repo.Owner = &User{Id: repo.OwnerId}
-		has, err := orm.Get(repo.Owner)
+		has, err := x.Get(repo.Owner)
 		if err != nil {
 			return nil, err
 		} else if !has {
@@ -550,11 +628,11 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error
 
 	// Update accesses.
 	accesses := make([]Access, 0, 10)
-	if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil {
+	if err = x.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil {
 		return err
 	}
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -615,11 +693,11 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error
 func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
 	// Update accesses.
 	accesses := make([]Access, 0, 10)
-	if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
+	if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
 		return err
 	}
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -650,25 +728,26 @@ func UpdateRepository(repo *Repository) error {
 	if len(repo.Website) > 255 {
 		repo.Website = repo.Website[:255]
 	}
-	_, err := orm.Id(repo.Id).AllCols().Update(repo)
+	_, err := x.Id(repo.Id).AllCols().Update(repo)
 	return err
 }
 
 // DeleteRepository deletes a repository for a user or orgnaztion.
-func DeleteRepository(userId, repoId int64, userName string) (err error) {
+func DeleteRepository(userId, repoId int64, userName string) error {
 	repo := &Repository{Id: repoId, OwnerId: userId}
-	has, err := orm.Get(repo)
+	has, err := x.Get(repo)
 	if err != nil {
 		return err
 	} else if !has {
 		return ErrRepoNotExist
 	}
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
 	}
+
 	if _, err = sess.Delete(&Repository{Id: repoId}); err != nil {
 		sess.Rollback()
 		return err
@@ -703,7 +782,7 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
 	}
 
 	// Delete comments.
-	if err = orm.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error {
+	if err = x.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error {
 		issue := bean.(*Issue)
 		if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
 			sess.Rollback()
@@ -725,16 +804,11 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
 		sess.Rollback()
 		return err
 	}
-	if err = sess.Commit(); err != nil {
-		sess.Rollback()
-		return err
-	}
 	if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
-		// TODO: log and delete manully
-		log.Error("delete repo %s/%s failed: %v", userName, repo.Name, err)
+		sess.Rollback()
 		return err
 	}
-	return nil
+	return sess.Commit()
 }
 
 // GetRepositoryByName returns the repository by given name under user if exists.
@@ -743,7 +817,7 @@ func GetRepositoryByName(userId int64, repoName string) (*Repository, error) {
 		OwnerId:   userId,
 		LowerName: strings.ToLower(repoName),
 	}
-	has, err := orm.Get(repo)
+	has, err := x.Get(repo)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -755,7 +829,7 @@ func GetRepositoryByName(userId int64, repoName string) (*Repository, error) {
 // GetRepositoryById returns the repository by given id if exists.
 func GetRepositoryById(id int64) (*Repository, error) {
 	repo := &Repository{}
-	has, err := orm.Id(id).Get(repo)
+	has, err := x.Id(id).Get(repo)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -767,7 +841,7 @@ func GetRepositoryById(id int64) (*Repository, error) {
 // GetRepositories returns a list of repositories of given user.
 func GetRepositories(uid int64, private bool) ([]*Repository, error) {
 	repos := make([]*Repository, 0, 10)
-	sess := orm.Desc("updated")
+	sess := x.Desc("updated")
 	if !private {
 		sess.Where("is_private=?", false)
 	}
@@ -778,19 +852,19 @@ func GetRepositories(uid int64, private bool) ([]*Repository, error) {
 
 // GetRecentUpdatedRepositories returns the list of repositories that are recently updated.
 func GetRecentUpdatedRepositories() (repos []*Repository, err error) {
-	err = orm.Where("is_private=?", false).Limit(5).Desc("updated").Find(&repos)
+	err = x.Where("is_private=?", false).Limit(5).Desc("updated").Find(&repos)
 	return repos, err
 }
 
 // GetRepositoryCount returns the total number of repositories of user.
 func GetRepositoryCount(user *User) (int64, error) {
-	return orm.Count(&Repository{OwnerId: user.Id})
+	return x.Count(&Repository{OwnerId: user.Id})
 }
 
 // GetCollaboratorNames returns a list of user name of repository's collaborators.
 func GetCollaboratorNames(repoName string) ([]string, error) {
 	accesses := make([]*Access, 0, 10)
-	if err := orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
+	if err := x.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
 		return nil, err
 	}
 
@@ -805,17 +879,17 @@ func GetCollaboratorNames(repoName string) ([]string, error) {
 func GetCollaborativeRepos(uname string) ([]*Repository, error) {
 	uname = strings.ToLower(uname)
 	accesses := make([]*Access, 0, 10)
-	if err := orm.Find(&accesses, &Access{UserName: uname}); err != nil {
+	if err := x.Find(&accesses, &Access{UserName: uname}); err != nil {
 		return nil, err
 	}
 
 	repos := make([]*Repository, 0, 10)
 	for _, access := range accesses {
-		if strings.HasPrefix(access.RepoName, uname) {
+		infos := strings.Split(access.RepoName, "/")
+		if infos[0] == uname {
 			continue
 		}
 
-		infos := strings.Split(access.RepoName, "/")
 		u, err := GetUserByName(infos[0])
 		if err != nil {
 			return nil, err
@@ -834,7 +908,7 @@ func GetCollaborativeRepos(uname string) ([]*Repository, error) {
 // GetCollaborators returns a list of users of repository's collaborators.
 func GetCollaborators(repoName string) (us []*User, err error) {
 	accesses := make([]*Access, 0, 10)
-	if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
+	if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
 		return nil, err
 	}
 
@@ -858,18 +932,18 @@ type Watch struct {
 // Watch or unwatch repository.
 func WatchRepo(uid, rid int64, watch bool) (err error) {
 	if watch {
-		if _, err = orm.Insert(&Watch{RepoId: rid, UserId: uid}); err != nil {
+		if _, err = x.Insert(&Watch{RepoId: rid, UserId: uid}); err != nil {
 			return err
 		}
 
 		rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
-		_, err = orm.Exec(rawSql, rid)
+		_, err = x.Exec(rawSql, rid)
 	} else {
-		if _, err = orm.Delete(&Watch{0, uid, rid}); err != nil {
+		if _, err = x.Delete(&Watch{0, uid, rid}); err != nil {
 			return err
 		}
 		rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?"
-		_, err = orm.Exec(rawSql, rid)
+		_, err = x.Exec(rawSql, rid)
 	}
 	return err
 }
@@ -877,7 +951,7 @@ func WatchRepo(uid, rid int64, watch bool) (err error) {
 // GetWatchers returns all watchers of given repository.
 func GetWatchers(rid int64) ([]*Watch, error) {
 	watches := make([]*Watch, 0, 10)
-	err := orm.Find(&watches, &Watch{RepoId: rid})
+	err := x.Find(&watches, &Watch{RepoId: rid})
 	return watches, err
 }
 
@@ -891,7 +965,7 @@ func NotifyWatchers(act *Action) error {
 
 	// Add feed for actioner.
 	act.UserId = act.ActUserId
-	if _, err = orm.InsertOne(act); err != nil {
+	if _, err = x.InsertOne(act); err != nil {
 		return errors.New("repo.NotifyWatchers(create action): " + err.Error())
 	}
 
@@ -902,7 +976,7 @@ func NotifyWatchers(act *Action) error {
 
 		act.Id = 0
 		act.UserId = watches[i].UserId
-		if _, err = orm.InsertOne(act); err != nil {
+		if _, err = x.InsertOne(act); err != nil {
 			return errors.New("repo.NotifyWatchers(create action): " + err.Error())
 		}
 	}
@@ -911,7 +985,7 @@ func NotifyWatchers(act *Action) error {
 
 // IsWatching checks if user has watched given repository.
 func IsWatching(uid, rid int64) bool {
-	has, _ := orm.Get(&Watch{0, uid, rid})
+	has, _ := x.Get(&Watch{0, uid, rid})
 	return has
 }
 

+ 15 - 16
models/update.go

@@ -6,21 +6,21 @@ package models
 
 import (
 	"container/list"
+	"fmt"
 	"os/exec"
 	"strings"
 
-	qlog "github.com/qiniu/log"
-
 	"github.com/gogits/git"
 
 	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/log"
 )
 
-func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) {
+func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
 	isNew := strings.HasPrefix(oldCommitId, "0000000")
 	if isNew &&
 		strings.HasPrefix(newCommitId, "0000000") {
-		qlog.Fatal("old rev and new rev both 000000")
+		return fmt.Errorf("old rev and new rev both 000000")
 	}
 
 	f := RepoPath(repoUserName, repoName)
@@ -31,19 +31,18 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
 
 	isDel := strings.HasPrefix(newCommitId, "0000000")
 	if isDel {
-		qlog.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
-		return
+		log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
+		return nil
 	}
 
 	repo, err := git.OpenRepository(f)
 	if err != nil {
-		qlog.Fatalf("runUpdate.Open repoId: %v", err)
+		return fmt.Errorf("runUpdate.Open repoId: %v", err)
 	}
 
 	newCommit, err := repo.GetCommit(newCommitId)
 	if err != nil {
-		qlog.Fatalf("runUpdate GetCommit of newCommitId: %v", err)
-		return
+		return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
 	}
 
 	var l *list.List
@@ -51,28 +50,27 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
 	if isNew {
 		l, err = newCommit.CommitsBefore()
 		if err != nil {
-			qlog.Fatalf("Find CommitsBefore erro: %v", err)
+			return fmt.Errorf("Find CommitsBefore erro: %v", err)
 		}
 	} else {
 		l, err = newCommit.CommitsBeforeUntil(oldCommitId)
 		if err != nil {
-			qlog.Fatalf("Find CommitsBeforeUntil erro: %v", err)
-			return
+			return fmt.Errorf("Find CommitsBeforeUntil erro: %v", err)
 		}
 	}
 
 	if err != nil {
-		qlog.Fatalf("runUpdate.Commit repoId: %v", err)
+		return fmt.Errorf("runUpdate.Commit repoId: %v", err)
 	}
 
 	ru, err := GetUserByName(repoUserName)
 	if err != nil {
-		qlog.Fatalf("runUpdate.GetUserByName: %v", err)
+		return fmt.Errorf("runUpdate.GetUserByName: %v", err)
 	}
 
 	repos, err := GetRepositoryByName(ru.Id, repoName)
 	if err != nil {
-		qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
+		return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
 	}
 
 	commits := make([]*base.PushCommit, 0)
@@ -96,6 +94,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
 	//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
 	if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
 		repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
-		qlog.Fatalf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
+		return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
 	}
+	return nil
 }

+ 134 - 87
models/user.go

@@ -21,14 +21,16 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-// User types.
+type UserType int
+
 const (
-	UT_INDIVIDUAL = iota + 1
-	UT_ORGANIZATION
+	INDIVIDUAL UserType = iota // Historic reason to make it starts at 0.
+	ORGANIZATION
 )
 
 var (
 	ErrUserOwnRepos          = errors.New("User still have ownership of repositories")
+	ErrUserHasOrgs           = errors.New("User still have membership of organization")
 	ErrUserAlreadyExist      = errors.New("User already exist")
 	ErrUserNotExist          = errors.New("User does not exist")
 	ErrUserNotKeyOwner       = errors.New("User does not the owner of public key")
@@ -47,10 +49,11 @@ type User struct {
 	FullName      string
 	Email         string `xorm:"unique not null"`
 	Passwd        string `xorm:"not null"`
-	LoginType     int
+	LoginType     LoginType
 	LoginSource   int64 `xorm:"not null default 0"`
 	LoginName     string
-	Type          int
+	Type          UserType
+	Orgs          []*User `xorm:"-"`
 	NumFollowers  int
 	NumFollowings int
 	NumStars      int
@@ -65,43 +68,63 @@ type User struct {
 	Salt          string    `xorm:"VARCHAR(10)"`
 	Created       time.Time `xorm:"created"`
 	Updated       time.Time `xorm:"updated"`
+
+	// For organization.
+	Description string
+	NumTeams    int
+	NumMembers  int
 }
 
 // HomeLink returns the user home page link.
-func (user *User) HomeLink() string {
-	return "/user/" + user.Name
+func (u *User) HomeLink() string {
+	return "/user/" + u.Name
 }
 
-// AvatarLink returns the user gravatar link.
-func (user *User) AvatarLink() string {
+// AvatarLink returns user gravatar link.
+func (u *User) AvatarLink() string {
 	if setting.DisableGravatar {
 		return "/img/avatar_default.jpg"
 	} else if setting.Service.EnableCacheAvatar {
-		return "/avatar/" + user.Avatar
+		return "/avatar/" + u.Avatar
 	}
-	return "//1.gravatar.com/avatar/" + user.Avatar
+	return "//1.gravatar.com/avatar/" + u.Avatar
 }
 
 // NewGitSig generates and returns the signature of given user.
-func (user *User) NewGitSig() *git.Signature {
+func (u *User) NewGitSig() *git.Signature {
 	return &git.Signature{
-		Name:  user.Name,
-		Email: user.Email,
+		Name:  u.Name,
+		Email: u.Email,
 		When:  time.Now(),
 	}
 }
 
 // EncodePasswd encodes password to safe format.
-func (user *User) EncodePasswd() {
-	newPasswd := base.PBKDF2([]byte(user.Passwd), []byte(user.Salt), 10000, 50, sha256.New)
-	user.Passwd = fmt.Sprintf("%x", newPasswd)
+func (u *User) EncodePasswd() {
+	newPasswd := base.PBKDF2([]byte(u.Passwd), []byte(u.Salt), 10000, 50, sha256.New)
+	u.Passwd = fmt.Sprintf("%x", newPasswd)
 }
 
-// Member represents user is member of organization.
-type Member struct {
-	Id     int64
-	OrgId  int64 `xorm:"unique(member) index"`
-	UserId int64 `xorm:"unique(member)"`
+// IsOrganization returns true if user is actually a organization.
+func (u *User) IsOrganization() bool {
+	return u.Type == ORGANIZATION
+}
+
+// GetOrganizations returns all organizations that user belongs to.
+func (u *User) GetOrganizations() error {
+	ous, err := GetOrgUsersByUserId(u.Id)
+	if err != nil {
+		return err
+	}
+
+	u.Orgs = make([]*User, len(ous))
+	for i, ou := range ous {
+		u.Orgs[i], err = GetUserById(ou.OrgId)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
 // IsUserExist checks if given user name exist,
@@ -110,7 +133,7 @@ func IsUserExist(name string) (bool, error) {
 	if len(name) == 0 {
 		return false, nil
 	}
-	return orm.Get(&User{LowerName: strings.ToLower(name)})
+	return x.Get(&User{LowerName: strings.ToLower(name)})
 }
 
 // IsEmailUsed returns true if the e-mail has been used.
@@ -118,7 +141,7 @@ func IsEmailUsed(email string) (bool, error) {
 	if len(email) == 0 {
 		return false, nil
 	}
-	return orm.Get(&User{Email: email})
+	return x.Get(&User{Email: email})
 }
 
 // GetUserSalt returns a user salt token
@@ -126,55 +149,66 @@ func GetUserSalt() string {
 	return base.GetRandomString(10)
 }
 
-// RegisterUser creates record of a new user.
-func RegisterUser(user *User) (*User, error) {
-
-	if !IsLegalName(user.Name) {
+// CreateUser creates record of a new user.
+func CreateUser(u *User) (*User, error) {
+	if !IsLegalName(u.Name) {
 		return nil, ErrUserNameIllegal
 	}
 
-	isExist, err := IsUserExist(user.Name)
+	isExist, err := IsUserExist(u.Name)
 	if err != nil {
 		return nil, err
 	} else if isExist {
 		return nil, ErrUserAlreadyExist
 	}
 
-	isExist, err = IsEmailUsed(user.Email)
+	isExist, err = IsEmailUsed(u.Email)
 	if err != nil {
 		return nil, err
 	} else if isExist {
 		return nil, ErrEmailAlreadyUsed
 	}
 
-	user.LowerName = strings.ToLower(user.Name)
-	user.Avatar = base.EncodeMd5(user.Email)
-	user.AvatarEmail = user.Email
-	user.Rands = GetUserSalt()
-	user.Salt = GetUserSalt()
-	user.EncodePasswd()
-	if _, err = orm.Insert(user); err != nil {
+	u.LowerName = strings.ToLower(u.Name)
+	u.Avatar = base.EncodeMd5(u.Email)
+	u.AvatarEmail = u.Email
+	u.Rands = GetUserSalt()
+	u.Salt = GetUserSalt()
+	u.EncodePasswd()
+
+	sess := x.NewSession()
+	defer sess.Close()
+	if err = sess.Begin(); err != nil {
 		return nil, err
-	} else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil {
-		if _, err := orm.Id(user.Id).Delete(&User{}); err != nil {
-			return nil, errors.New(fmt.Sprintf(
-				"both create userpath %s and delete table record faild: %v", user.Name, err))
-		}
+	}
+
+	if _, err = sess.Insert(u); err != nil {
+		sess.Rollback()
 		return nil, err
 	}
 
-	if user.Id == 1 {
-		user.IsAdmin = true
-		user.IsActive = true
-		_, err = orm.Id(user.Id).UseBool().Update(user)
+	if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil {
+		sess.Rollback()
+		return nil, err
 	}
-	return user, err
+
+	if err = sess.Commit(); err != nil {
+		return nil, err
+	}
+
+	// Auto-set admin for user whose ID is 1.
+	if u.Id == 1 {
+		u.IsAdmin = true
+		u.IsActive = true
+		_, err = x.Id(u.Id).UseBool().Update(u)
+	}
+	return u, err
 }
 
 // GetUsers returns given number of user objects with offset.
 func GetUsers(num, offset int) ([]User, error) {
 	users := make([]User, 0, num)
-	err := orm.Limit(num, offset).Asc("id").Find(&users)
+	err := x.Limit(num, offset).Asc("id").Find(&users)
 	return users, err
 }
 
@@ -218,11 +252,11 @@ func ChangeUserName(user *User, newUserName string) (err error) {
 
 	// Update accesses of user.
 	accesses := make([]Access, 0, 10)
-	if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil {
+	if err = x.Find(&accesses, &Access{UserName: user.LowerName}); err != nil {
 		return err
 	}
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
@@ -245,7 +279,7 @@ func ChangeUserName(user *User, newUserName string) (err error) {
 	for i := range repos {
 		accesses = make([]Access, 0, 10)
 		// Update accesses of user repository.
-		if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repos[i].LowerName}); err != nil {
+		if err = x.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repos[i].LowerName}); err != nil {
 			return err
 		}
 
@@ -268,60 +302,68 @@ func ChangeUserName(user *User, newUserName string) (err error) {
 }
 
 // UpdateUser updates user's information.
-func UpdateUser(user *User) (err error) {
-	user.LowerName = strings.ToLower(user.Name)
+func UpdateUser(u *User) (err error) {
+	u.LowerName = strings.ToLower(u.Name)
 
-	if len(user.Location) > 255 {
-		user.Location = user.Location[:255]
+	if len(u.Location) > 255 {
+		u.Location = u.Location[:255]
+	}
+	if len(u.Website) > 255 {
+		u.Website = u.Website[:255]
 	}
-	if len(user.Website) > 255 {
-		user.Website = user.Website[:255]
+	if len(u.Description) > 255 {
+		u.Description = u.Description[:255]
 	}
 
-	_, err = orm.Id(user.Id).AllCols().Update(user)
+	_, err = x.Id(u.Id).AllCols().Update(u)
 	return err
 }
 
-// DeleteUser completely deletes everything of the user.
-func DeleteUser(user *User) error {
+// TODO: need some kind of mechanism to record failure.
+// DeleteUser completely and permanently deletes everything of user.
+func DeleteUser(u *User) error {
 	// Check ownership of repository.
-	count, err := GetRepositoryCount(user)
+	count, err := GetRepositoryCount(u)
 	if err != nil {
-		return errors.New("modesl.GetRepositories: " + err.Error())
+		return errors.New("modesl.GetRepositories(GetRepositoryCount): " + err.Error())
 	} else if count > 0 {
 		return ErrUserOwnRepos
 	}
 
+	// Check membership of organization.
+	count, err = GetOrganizationCount(u)
+	if err != nil {
+		return errors.New("modesl.GetRepositories(GetOrganizationCount): " + err.Error())
+	} else if count > 0 {
+		return ErrUserHasOrgs
+	}
+
 	// TODO: check issues, other repos' commits
+	// TODO: roll backable in some point.
 
 	// Delete all followers.
-	if _, err = orm.Delete(&Follow{FollowId: user.Id}); err != nil {
+	if _, err = x.Delete(&Follow{FollowId: u.Id}); err != nil {
 		return err
 	}
-
 	// Delete oauth2.
-	if _, err = orm.Delete(&Oauth2{Uid: user.Id}); err != nil {
+	if _, err = x.Delete(&Oauth2{Uid: u.Id}); err != nil {
 		return err
 	}
-
 	// Delete all feeds.
-	if _, err = orm.Delete(&Action{UserId: user.Id}); err != nil {
+	if _, err = x.Delete(&Action{UserId: u.Id}); err != nil {
 		return err
 	}
-
 	// Delete all watches.
-	if _, err = orm.Delete(&Watch{UserId: user.Id}); err != nil {
+	if _, err = x.Delete(&Watch{UserId: u.Id}); err != nil {
 		return err
 	}
-
 	// Delete all accesses.
-	if _, err = orm.Delete(&Access{UserName: user.LowerName}); err != nil {
+	if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil {
 		return err
 	}
-
 	// Delete all SSH keys.
 	keys := make([]*PublicKey, 0, 10)
-	if err = orm.Find(&keys, &PublicKey{OwnerId: user.Id}); err != nil {
+	if err = x.Find(&keys, &PublicKey{OwnerId: u.Id}); err != nil {
 		return err
 	}
 	for _, key := range keys {
@@ -331,11 +373,17 @@ func DeleteUser(user *User) error {
 	}
 
 	// Delete user directory.
-	if err = os.RemoveAll(UserPath(user.Name)); err != nil {
+	if err = os.RemoveAll(UserPath(u.Name)); err != nil {
 		return err
 	}
 
-	_, err = orm.Delete(user)
+	_, err = x.Delete(u)
+	return err
+}
+
+// DeleteInactivateUsers deletes all inactivate users.
+func DeleteInactivateUsers() error {
+	_, err := x.Where("is_active=?", false).Delete(new(User))
 	return err
 }
 
@@ -347,7 +395,7 @@ func UserPath(userName string) string {
 func GetUserByKeyId(keyId int64) (*User, error) {
 	user := new(User)
 	rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?"
-	has, err := orm.Sql(rawSql, keyId).Get(user)
+	has, err := x.Sql(rawSql, keyId).Get(user)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -356,17 +404,16 @@ func GetUserByKeyId(keyId int64) (*User, error) {
 	return user, nil
 }
 
-// GetUserById returns the user object by given id if exists.
+// GetUserById returns the user object by given ID if exists.
 func GetUserById(id int64) (*User, error) {
-	user := new(User)
-	has, err := orm.Id(id).Get(user)
+	u := new(User)
+	has, err := x.Id(id).Get(u)
 	if err != nil {
 		return nil, err
-	}
-	if !has {
+	} else if !has {
 		return nil, ErrUserNotExist
 	}
-	return user, nil
+	return u, nil
 }
 
 // GetUserByName returns the user object by given name if exists.
@@ -375,7 +422,7 @@ func GetUserByName(name string) (*User, error) {
 		return nil, ErrUserNotExist
 	}
 	user := &User{LowerName: strings.ToLower(name)}
-	has, err := orm.Get(user)
+	has, err := x.Get(user)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -416,7 +463,7 @@ func GetUserByEmail(email string) (*User, error) {
 		return nil, ErrUserNotExist
 	}
 	user := &User{Email: strings.ToLower(email)}
-	has, err := orm.Get(user)
+	has, err := x.Get(user)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -440,7 +487,7 @@ func SearchUserByName(key string, limit int) (us []*User, err error) {
 	key = strings.ToLower(key)
 
 	us = make([]*User, 0, limit)
-	err = orm.Limit(limit).Where("lower_name like '%" + key + "%'").Find(&us)
+	err = x.Limit(limit).Where("lower_name like '%" + key + "%'").Find(&us)
 	return us, err
 }
 
@@ -453,7 +500,7 @@ type Follow struct {
 
 // FollowUser marks someone be another's follower.
 func FollowUser(userId int64, followId int64) (err error) {
-	session := orm.NewSession()
+	session := x.NewSession()
 	defer session.Close()
 	session.Begin()
 
@@ -478,7 +525,7 @@ func FollowUser(userId int64, followId int64) (err error) {
 
 // UnFollowUser unmarks someone be another's follower.
 func UnFollowUser(userId int64, unFollowId int64) (err error) {
-	session := orm.NewSession()
+	session := x.NewSession()
 	defer session.Close()
 	session.Begin()
 

+ 126 - 17
models/webhook.go

@@ -7,29 +7,35 @@ package models
 import (
 	"encoding/json"
 	"errors"
+	"time"
 
+	"github.com/gogits/gogs/modules/httplib"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/setting"
 )
 
 var (
 	ErrWebhookNotExist = errors.New("Webhook does not exist")
 )
 
-// Content types.
+type HookContentType int
+
 const (
-	CT_JSON = iota + 1
-	CT_FORM
+	JSON HookContentType = iota + 1
+	FORM
 )
 
+// HookEvent represents events that will delivery hook.
 type HookEvent struct {
 	PushOnly bool `json:"push_only"`
 }
 
+// Webhook represents a web hook object.
 type Webhook struct {
 	Id          int64
 	RepoId      int64
 	Url         string `xorm:"TEXT"`
-	ContentType int
+	ContentType HookContentType
 	Secret      string `xorm:"TEXT"`
 	Events      string `xorm:"TEXT"`
 	*HookEvent  `xorm:"-"`
@@ -37,6 +43,7 @@ type Webhook struct {
 	IsActive    bool
 }
 
+// GetEvent handles conversion from Events to HookEvent.
 func (w *Webhook) GetEvent() {
 	w.HookEvent = &HookEvent{}
 	if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
@@ -44,12 +51,14 @@ func (w *Webhook) GetEvent() {
 	}
 }
 
-func (w *Webhook) SaveEvent() error {
+// UpdateEvent handles conversion from HookEvent to Events.
+func (w *Webhook) UpdateEvent() error {
 	data, err := json.Marshal(w.HookEvent)
 	w.Events = string(data)
 	return err
 }
 
+// HasPushEvent returns true if hook enbaled push event.
 func (w *Webhook) HasPushEvent() bool {
 	if w.PushOnly {
 		return true
@@ -57,22 +66,16 @@ func (w *Webhook) HasPushEvent() bool {
 	return false
 }
 
-// CreateWebhook creates new webhook.
+// CreateWebhook creates a new web hook.
 func CreateWebhook(w *Webhook) error {
-	_, err := orm.Insert(w)
-	return err
-}
-
-// UpdateWebhook updates information of webhook.
-func UpdateWebhook(w *Webhook) error {
-	_, err := orm.AllCols().Update(w)
+	_, err := x.Insert(w)
 	return err
 }
 
 // GetWebhookById returns webhook by given ID.
 func GetWebhookById(hookId int64) (*Webhook, error) {
 	w := &Webhook{Id: hookId}
-	has, err := orm.Get(w)
+	has, err := x.Get(w)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -83,18 +86,124 @@ func GetWebhookById(hookId int64) (*Webhook, error) {
 
 // GetActiveWebhooksByRepoId returns all active webhooks of repository.
 func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
-	err = orm.Find(&ws, &Webhook{RepoId: repoId, IsActive: true})
+	err = x.Find(&ws, &Webhook{RepoId: repoId, IsActive: true})
 	return ws, err
 }
 
 // GetWebhooksByRepoId returns all webhooks of repository.
 func GetWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
-	err = orm.Find(&ws, &Webhook{RepoId: repoId})
+	err = x.Find(&ws, &Webhook{RepoId: repoId})
 	return ws, err
 }
 
+// UpdateWebhook updates information of webhook.
+func UpdateWebhook(w *Webhook) error {
+	_, err := x.AllCols().Update(w)
+	return err
+}
+
 // DeleteWebhook deletes webhook of repository.
 func DeleteWebhook(hookId int64) error {
-	_, err := orm.Delete(&Webhook{Id: hookId})
+	_, err := x.Delete(&Webhook{Id: hookId})
+	return err
+}
+
+//   ___ ___                __   ___________              __
+//  /   |   \  ____   ____ |  | _\__    ___/____    _____|  | __
+// /    ~    \/  _ \ /  _ \|  |/ / |    |  \__  \  /  ___/  |/ /
+// \    Y    (  <_> |  <_> )    <  |    |   / __ \_\___ \|    <
+//  \___|_  / \____/ \____/|__|_ \ |____|  (____  /____  >__|_ \
+//        \/                    \/              \/     \/     \/
+
+type HookTaskType int
+
+const (
+	WEBHOOK HookTaskType = iota + 1
+	SERVICE
+)
+
+type PayloadAuthor struct {
+	Name  string `json:"name"`
+	Email string `json:"email"`
+}
+
+type PayloadCommit struct {
+	Id      string         `json:"id"`
+	Message string         `json:"message"`
+	Url     string         `json:"url"`
+	Author  *PayloadAuthor `json:"author"`
+}
+
+type PayloadRepo struct {
+	Id          int64          `json:"id"`
+	Name        string         `json:"name"`
+	Url         string         `json:"url"`
+	Description string         `json:"description"`
+	Website     string         `json:"website"`
+	Watchers    int            `json:"watchers"`
+	Owner       *PayloadAuthor `json:"author"`
+	Private     bool           `json:"private"`
+}
+
+// Payload represents a payload information of hook.
+type Payload struct {
+	Secret  string           `json:"secret"`
+	Ref     string           `json:"ref"`
+	Commits []*PayloadCommit `json:"commits"`
+	Repo    *PayloadRepo     `json:"repository"`
+	Pusher  *PayloadAuthor   `json:"pusher"`
+}
+
+// HookTask represents a hook task.
+type HookTask struct {
+	Id             int64
+	Type           HookTaskType
+	Url            string
+	*Payload       `xorm:"-"`
+	PayloadContent string `xorm:"TEXT"`
+	ContentType    HookContentType
+	IsSsl          bool
+	IsDeliveried   bool
+}
+
+// CreateHookTask creates a new hook task,
+// it handles conversion from Payload to PayloadContent.
+func CreateHookTask(t *HookTask) error {
+	data, err := json.Marshal(t.Payload)
+	if err != nil {
+		return err
+	}
+	t.PayloadContent = string(data)
+	_, err = x.Insert(t)
 	return err
 }
+
+// UpdateHookTask updates information of hook task.
+func UpdateHookTask(t *HookTask) error {
+	_, err := x.AllCols().Update(t)
+	return err
+}
+
+// DeliverHooks checks and delivers undelivered hooks.
+func DeliverHooks() {
+	timeout := time.Duration(setting.WebhookDeliverTimeout) * time.Second
+	x.Where("is_deliveried=?", false).Iterate(new(HookTask),
+		func(idx int, bean interface{}) error {
+			t := bean.(*HookTask)
+			// Only support JSON now.
+			if _, err := httplib.Post(t.Url).SetTimeout(timeout, timeout).
+				Body([]byte(t.PayloadContent)).Response(); err != nil {
+				log.Error("webhook.DeliverHooks(Delivery): %v", err)
+				return nil
+			}
+
+			t.IsDeliveried = true
+			if err := UpdateHookTask(t); err != nil {
+				log.Error("webhook.DeliverHooks(UpdateHookTask): %v", err)
+				return nil
+			}
+
+			log.Trace("Hook delivered: %s", t.PayloadContent)
+			return nil
+		})
+}

+ 57 - 0
modules/auth/org.go

@@ -0,0 +1,57 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+	"net/http"
+	"reflect"
+
+	"github.com/go-martini/martini"
+
+	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/middleware/binding"
+)
+
+type CreateOrgForm struct {
+	OrgName string `form:"orgname" binding:"Required;AlphaDashDot;MaxSize(30)"`
+	Email   string `form:"email" binding:"Required;Email;MaxSize(50)"`
+}
+
+func (f *CreateOrgForm) Name(field string) string {
+	names := map[string]string{
+		"OrgName": "Organization name",
+		"Email":   "E-mail address",
+	}
+	return names[field]
+}
+
+func (f *CreateOrgForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
+	data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+	validate(errs, data, f)
+}
+
+type OrgSettingForm struct {
+	DisplayName string `form:"display_name" binding:"Required;MaxSize(100)"`
+	Email       string `form:"email" binding:"Required;Email;MaxSize(50)"`
+	Description string `form:"desc" binding:"MaxSize(255)"`
+	Website     string `form:"site" binding:"Url;MaxSize(100)"`
+	Location    string `form:"location" binding:"MaxSize(50)"`
+}
+
+func (f *OrgSettingForm) Name(field string) string {
+	names := map[string]string{
+		"DisplayName": "Display name",
+		"Email":       "E-mail address",
+		"Description": "Description",
+		"Website":     "Website address",
+		"Location":    "Location",
+	}
+	return names[field]
+}
+
+func (f *OrgSettingForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+	validate(errors, data, f)
+}

+ 30 - 3
modules/auth/repo.go

@@ -22,9 +22,10 @@ import (
 //         \/     \/|__|              \/                       \/
 
 type CreateRepoForm struct {
+	Uid         int64  `form:"uid" binding:"Required"`
 	RepoName    string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	Private     bool   `form:"private"`
-	Description string `form:"desc" binding:"MaxSize(100)"`
+	Description string `form:"desc" binding:"MaxSize(255)"`
 	Language    string `form:"language"`
 	License     string `form:"license"`
 	InitReadme  bool   `form:"initReadme"`
@@ -47,10 +48,11 @@ type MigrateRepoForm struct {
 	Url          string `form:"url" binding:"Url"`
 	AuthUserName string `form:"auth_username"`
 	AuthPasswd   string `form:"auth_password"`
+	Uid          int64  `form:"uid" binding:"Required"`
 	RepoName     string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	Mirror       bool   `form:"mirror"`
 	Private      bool   `form:"private"`
-	Description  string `form:"desc" binding:"MaxSize(100)"`
+	Description  string `form:"desc" binding:"MaxSize(255)"`
 }
 
 func (f *MigrateRepoForm) Name(field string) string {
@@ -69,7 +71,7 @@ func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, co
 
 type RepoSettingForm struct {
 	RepoName    string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
-	Description string `form:"desc" binding:"MaxSize(100)"`
+	Description string `form:"desc" binding:"MaxSize(255)"`
 	Website     string `form:"site" binding:"Url;MaxSize(100)"`
 	Branch      string `form:"branch"`
 	Interval    int    `form:"interval"`
@@ -205,14 +207,17 @@ func (f *CreateLabelForm) Validate(errors *binding.Errors, req *http.Request, co
 
 type NewReleaseForm struct {
 	TagName    string `form:"tag_name" binding:"Required"`
+	Target     string `form:"tag_target" binding:"Required"`
 	Title      string `form:"title" binding:"Required"`
 	Content    string `form:"content" binding:"Required"`
+	Draft      string `form:"draft"`
 	Prerelease bool   `form:"prerelease"`
 }
 
 func (f *NewReleaseForm) Name(field string) string {
 	names := map[string]string{
 		"TagName": "Tag name",
+		"Target":  "Target",
 		"Title":   "Release title",
 		"Content": "Release content",
 	}
@@ -223,3 +228,25 @@ func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, con
 	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
 	validate(errors, data, f)
 }
+
+type EditReleaseForm struct {
+	Target     string `form:"tag_target" binding:"Required"`
+	Title      string `form:"title" binding:"Required"`
+	Content    string `form:"content" binding:"Required"`
+	Draft      string `form:"draft"`
+	Prerelease bool   `form:"prerelease"`
+}
+
+func (f *EditReleaseForm) Name(field string) string {
+	names := map[string]string{
+		"Target":  "Target",
+		"Title":   "Release title",
+		"Content": "Release content",
+	}
+	return names[field]
+}
+
+func (f *EditReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+	validate(errors, data, f)
+}

+ 32 - 26
modules/auth/user.go

@@ -16,57 +16,63 @@ import (
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/middleware/binding"
+	"github.com/gogits/gogs/modules/setting"
 )
 
 // SignedInId returns the id of signed in user.
-func SignedInId(session session.SessionStore) int64 {
+func SignedInId(header http.Header, sess session.SessionStore) int64 {
 	if !models.HasEngine {
 		return 0
 	}
 
-	userId := session.Get("userId")
-	if userId == nil {
+	if setting.Service.EnableReverseProxyAuth {
+		webAuthUser := header.Get(setting.ReverseProxyAuthUser)
+		if len(webAuthUser) > 0 {
+			u, err := models.GetUserByName(webAuthUser)
+			if err != nil {
+				if err != models.ErrUserNotExist {
+					log.Error("auth.user.SignedInId(GetUserByName): %v", err)
+				}
+				return 0
+			}
+			return u.Id
+		}
+	}
+
+	uid := sess.Get("userId")
+	if uid == nil {
 		return 0
 	}
-	if s, ok := userId.(int64); ok {
-		if _, err := models.GetUserById(s); err != nil {
+	if id, ok := uid.(int64); ok {
+		if _, err := models.GetUserById(id); err != nil {
+			if err != models.ErrUserNotExist {
+				log.Error("auth.user.SignedInId(GetUserById): %v", err)
+			}
 			return 0
 		}
-		return s
+		return id
 	}
 	return 0
 }
 
-// SignedInName returns the name of signed in user.
-func SignedInName(session session.SessionStore) string {
-	userName := session.Get("userName")
-	if userName == nil {
-		return ""
-	}
-	if s, ok := userName.(string); ok {
-		return s
-	}
-	return ""
-}
-
 // SignedInUser returns the user object of signed user.
-func SignedInUser(session session.SessionStore) *models.User {
-	id := SignedInId(session)
-	if id <= 0 {
+func SignedInUser(header http.Header, sess session.SessionStore) *models.User {
+	uid := SignedInId(header, sess)
+	if uid <= 0 {
 		return nil
 	}
 
-	user, err := models.GetUserById(id)
+	u, err := models.GetUserById(uid)
 	if err != nil {
 		log.Error("user.SignedInUser: %v", err)
 		return nil
 	}
-	return user
+	return u
 }
 
 // IsSignedIn check if any user has signed in.
-func IsSignedIn(session session.SessionStore) bool {
-	return SignedInId(session) > 0
+func IsSignedIn(header http.Header, sess session.SessionStore) bool {
+	return SignedInId(header, sess) > 0
 }
 
 type FeedsForm struct {
@@ -87,7 +93,7 @@ func (f *UpdateProfileForm) Name(field string) string {
 	names := map[string]string{
 		"UserName": "Username",
 		"Email":    "E-mail address",
-		"Website":  "Website",
+		"Website":  "Website address",
 		"Location": "Location",
 		"Avatar":   "Gravatar Email",
 	}

+ 1 - 0
modules/base/base.go

@@ -7,6 +7,7 @@ package base
 type (
 	// Type TmplData represents data in the templates.
 	TmplData map[string]interface{}
+	TplName  string
 
 	ApiJsonErr struct {
 		Message string `json:"message"`

+ 262 - 247
modules/bin/conf.go

@@ -1,11 +1,11 @@
 package bin
 
 import (
-    "bytes"
-    "compress/gzip"
-    "fmt"
-    "io"
-    "strings"
+	"bytes"
+	"compress/gzip"
+	"fmt"
+	"io"
+	"strings"
 )
 
 func bindata_read(data []byte, name string) ([]byte, error) {
@@ -27,227 +27,243 @@ func bindata_read(data []byte, name string) ([]byte, error) {
 
 func conf_app_ini() ([]byte, error) {
 	return bindata_read([]byte{
-		0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xb4, 0x58,
-		0xed, 0x72, 0xdb, 0xb8, 0xd5, 0xfe, 0xcf, 0xab, 0x40, 0xf4, 0xee, 0xbe,
-		0x9b, 0x74, 0x6c, 0x49, 0x76, 0x1a, 0x27, 0x6b, 0x6f, 0x66, 0x56, 0x96,
-		0x28, 0x9b, 0x8d, 0xf5, 0x11, 0x92, 0x76, 0x9a, 0x66, 0x3c, 0x1c, 0x9a,
-		0x84, 0x24, 0xd4, 0x24, 0x41, 0x13, 0x90, 0x15, 0xf5, 0x5f, 0x6f, 0xa1,
-		0xd3, 0xab, 0xe9, 0xf5, 0xf4, 0x47, 0x2f, 0xa3, 0xcf, 0x01, 0x49, 0x99,
-		0x52, 0xb4, 0xd9, 0xf4, 0x6b, 0x76, 0x27, 0x16, 0x81, 0x83, 0x83, 0x73,
-		0x9e, 0xf3, 0x8d, 0x33, 0xd6, 0xcb, 0x73, 0x96, 0x85, 0x29, 0x67, 0x7a,
-		0x11, 0x6a, 0xa6, 0x16, 0x72, 0xa5, 0x98, 0xcc, 0x18, 0x7f, 0xe4, 0xc5,
-		0x9a, 0xe5, 0xe1, 0x1c, 0x1b, 0x42, 0x27, 0xdc, 0xea, 0x4d, 0xa7, 0xc1,
-		0xb8, 0x37, 0xb2, 0xd9, 0x5b, 0x76, 0x21, 0xe7, 0xea, 0x14, 0xff, 0xb2,
-		0x0b, 0xa1, 0x99, 0xc7, 0x8b, 0x47, 0x11, 0x95, 0xfb, 0x57, 0x93, 0x8b,
-		0x09, 0xf6, 0x45, 0x3a, 0xef, 0xcc, 0x42, 0xac, 0xca, 0xac, 0x9d, 0x67,
-		0x73, 0xeb, 0x8c, 0xf5, 0x17, 0x61, 0x06, 0x4e, 0x20, 0x17, 0x33, 0xb6,
-		0x96, 0x4b, 0x56, 0x2c, 0x33, 0x96, 0xc8, 0x28, 0x4c, 0x92, 0xb5, 0xe5,
-		0x5e, 0x8f, 0x83, 0x6b, 0xcf, 0x76, 0x71, 0x72, 0x2e, 0x34, 0xa8, 0x6d,
-		0xa1, 0x17, 0xbc, 0x60, 0xad, 0x98, 0x3f, 0xb6, 0x0e, 0x58, 0x2b, 0x2f,
-		0x64, 0xdc, 0x62, 0x12, 0x0b, 0x9a, 0x2b, 0x8d, 0x95, 0x98, 0xcf, 0xc2,
-		0x65, 0x02, 0x5e, 0xaa, 0xa4, 0x31, 0x1c, 0x46, 0x93, 0x01, 0xc9, 0x86,
-		0x6f, 0xcb, 0xfa, 0x54, 0xf0, 0x5c, 0x2a, 0xa1, 0x65, 0xb1, 0xbe, 0xb5,
-		0xdc, 0xc9, 0xc4, 0xc7, 0x86, 0xe5, 0xf5, 0x5d, 0x67, 0xea, 0x07, 0xfe,
-		0xc7, 0x29, 0xd1, 0xdd, 0x85, 0x6a, 0x01, 0x42, 0x05, 0xe9, 0x79, 0x71,
-		0x6b, 0x4d, 0xdd, 0x89, 0x3f, 0xe9, 0x4f, 0xae, 0xb0, 0xb3, 0xd0, 0x3a,
-		0xb7, 0x06, 0x93, 0x51, 0xcf, 0x19, 0xe3, 0xcb, 0x08, 0xb9, 0x90, 0x4a,
-		0x1b, 0x3e, 0xc1, 0xb5, 0x4b, 0x24, 0xdf, 0x3f, 0xaf, 0xe9, 0x5f, 0xa8,
-		0xd3, 0x4e, 0xe7, 0xfb, 0xe7, 0x25, 0x39, 0x3e, 0xbe, 0x7f, 0x7e, 0xe9,
-		0xfb, 0xd3, 0x60, 0x3a, 0x71, 0xfd, 0x17, 0xaa, 0x63, 0x99, 0x8f, 0xde,
-		0x60, 0x40, 0xba, 0x59, 0x9b, 0x1d, 0x7c, 0xbc, 0xec, 0x76, 0xbb, 0x96,
-		0xe7, 0x5d, 0xd6, 0xdf, 0xc7, 0xc7, 0xd0, 0x7b, 0x20, 0x54, 0x78, 0x97,
-		0x70, 0xd6, 0x1f, 0x8c, 0x09, 0xff, 0x8c, 0x89, 0xac, 0xd6, 0x3e, 0x95,
-		0x31, 0xb7, 0x26, 0xc3, 0xe1, 0x95, 0x33, 0xb6, 0x6b, 0x55, 0x67, 0x61,
-		0xa2, 0xb8, 0x35, 0x70, 0xbc, 0xde, 0xf9, 0x95, 0x1d, 0xb8, 0x93, 0x6b,
-		0xdf, 0x76, 0xc9, 0x04, 0x9b, 0xad, 0x33, 0x76, 0xc1, 0x33, 0x5e, 0x84,
-		0x9a, 0x33, 0xa5, 0x79, 0xae, 0x4e, 0xb1, 0xf2, 0x1d, 0x8b, 0x62, 0x98,
-		0x55, 0x2f, 0x3a, 0x5a, 0x76, 0xe6, 0x30, 0x64, 0x27, 0x5a, 0x2a, 0x2d,
-		0xd3, 0x0e, 0xa9, 0xad, 0x0c, 0xc1, 0x5c, 0x1a, 0xf3, 0x7c, 0x77, 0x31,
-		0x21, 0x95, 0x3b, 0xaa, 0x88, 0x3a, 0xf9, 0xfd, 0xbc, 0x13, 0x15, 0xeb,
-		0x1c, 0x67, 0x74, 0xa2, 0x3a, 0xf3, 0x8a, 0x6d, 0x10, 0xf1, 0x42, 0xb7,
-		0x41, 0x7f, 0x18, 0x85, 0x6f, 0x75, 0xb1, 0xe4, 0xec, 0x30, 0x5e, 0x62,
-		0x43, 0xc8, 0xec, 0xed, 0x9b, 0xd7, 0x27, 0xdd, 0x45, 0x37, 0xed, 0x2a,
-		0x76, 0x48, 0xf0, 0xbd, 0x4d, 0xd7, 0xf4, 0xa7, 0xcd, 0x3f, 0x87, 0x69,
-		0x9e, 0xf0, 0x76, 0x24, 0x53, 0xab, 0x6f, 0xbb, 0x7e, 0x30, 0x74, 0xae,
-		0x48, 0x99, 0xa6, 0x14, 0x1d, 0xc3, 0x36, 0xe7, 0xa9, 0xf5, 0xce, 0xfe,
-		0xb8, 0x97, 0xe0, 0x9e, 0xaf, 0xcd, 0xfe, 0x19, 0xbb, 0xce, 0x73, 0xb8,
-		0x4a, 0x02, 0xb8, 0x12, 0x26, 0x67, 0x4c, 0x73, 0x70, 0x27, 0x85, 0xc3,
-		0x2c, 0x86, 0xd2, 0x10, 0x25, 0x62, 0x33, 0x01, 0x4c, 0x49, 0x65, 0x90,
-		0x37, 0x5c, 0x07, 0x3e, 0x66, 0x56, 0xd9, 0x0a, 0xce, 0xc6, 0x8d, 0x53,
-		0xd3, 0x32, 0xff, 0xcc, 0xa3, 0xa5, 0xe6, 0xb1, 0xe5, 0xf9, 0x3d, 0xdf,
-		0xe9, 0x07, 0xc6, 0xec, 0xd3, 0x9e, 0x7f, 0x49, 0x26, 0xb4, 0x3e, 0xc5,
-		0xa1, 0x0e, 0xe1, 0x3b, 0xfc, 0xb6, 0xe1, 0xa7, 0xe9, 0x5a, 0x3d, 0x24,
-		0xc6, 0x53, 0xa1, 0xe1, 0xbc, 0xe0, 0xaa, 0xf4, 0x56, 0x2c, 0x0a, 0xcd,
-		0x5f, 0x62, 0x43, 0xe8, 0x1f, 0x14, 0xb9, 0x7d, 0xc1, 0xa2, 0x85, 0xa4,
-		0x60, 0x19, 0x9c, 0xd7, 0x7e, 0x68, 0xce, 0x5a, 0x97, 0x13, 0x8f, 0xbc,
-		0xe0, 0xe8, 0xf8, 0x75, 0xbb, 0x8b, 0xff, 0x8e, 0x4e, 0x5f, 0xbe, 0xec,
-		0x9e, 0x58, 0x55, 0xb8, 0x91, 0x95, 0xac, 0x2a, 0x40, 0x0a, 0x29, 0xb5,
-		0x35, 0xed, 0x79, 0xde, 0x87, 0x01, 0x7b, 0x0b, 0x11, 0x86, 0x74, 0x51,
-		0xe3, 0xda, 0x2c, 0x59, 0x1f, 0x30, 0x5e, 0xc7, 0x4f, 0xe9, 0x4f, 0x24,
-		0x59, 0xc1, 0x1f, 0x96, 0xa2, 0xe0, 0xa5, 0x60, 0xf0, 0x78, 0x31, 0x5b,
-		0x1f, 0xce, 0x96, 0x49, 0xd2, 0x82, 0x13, 0x5e, 0x6d, 0x62, 0xa7, 0xa4,
-		0xaf, 0xd9, 0xd6, 0xf2, 0x1b, 0xae, 0x56, 0x05, 0x01, 0xe9, 0x6f, 0xfc,
-		0xa6, 0x1d, 0xdf, 0x01, 0x8e, 0x30, 0x4e, 0x45, 0x76, 0x6b, 0x02, 0x29,
-		0x5a, 0x16, 0x42, 0x23, 0xde, 0x9c, 0x31, 0x90, 0xbb, 0xba, 0x82, 0x27,
-		0xf6, 0xdf, 0x35, 0x5c, 0xf1, 0xd9, 0xb3, 0xfe, 0x65, 0x6f, 0x7c, 0x61,
-		0x33, 0xff, 0xd2, 0xf1, 0x98, 0x3f, 0x61, 0xef, 0x6c, 0x7b, 0xca, 0x3e,
-		0x4e, 0xae, 0x5d, 0x66, 0x74, 0x1b, 0xf4, 0xfc, 0x1e, 0xf3, 0x7a, 0x43,
-		0xfb, 0xd9, 0x33, 0xcb, 0xb3, 0xfb, 0xae, 0xed, 0x07, 0xb0, 0x3e, 0x18,
-		0x3c, 0xfb, 0xbf, 0x9f, 0x87, 0x03, 0xfb, 0x83, 0x8b, 0xff, 0xff, 0xff,
-		0x37, 0xcf, 0xc1, 0xa9, 0xb7, 0xd4, 0xf2, 0x30, 0x91, 0x73, 0x44, 0x47,
-		0xc1, 0x53, 0x9e, 0xde, 0x41, 0xd7, 0x38, 0x5c, 0x2b, 0x0b, 0xbe, 0xef,
-		0x8c, 0x03, 0xd7, 0x1e, 0xd9, 0xa3, 0x73, 0x84, 0xc2, 0xa0, 0xf7, 0xd1,
-		0xc3, 0xf9, 0xd7, 0x56, 0x7f, 0x32, 0x79, 0xe7, 0xd8, 0x26, 0xc7, 0x34,
-		0x20, 0x0d, 0xc2, 0x15, 0x57, 0x32, 0xe5, 0xf5, 0xf6, 0xe6, 0x5c, 0x93,
-		0x46, 0x64, 0x51, 0xc1, 0x63, 0x41, 0xa8, 0x94, 0xc9, 0x02, 0xd6, 0xbb,
-		0xb5, 0x7a, 0x7d, 0xdf, 0xb9, 0xb1, 0x83, 0x3e, 0x60, 0x0b, 0xae, 0xe8,
-		0xd7, 0xc8, 0x19, 0x23, 0xfa, 0xe8, 0xb6, 0xa3, 0x37, 0x5d, 0xcb, 0xb5,
-		0x3d, 0x9b, 0x7c, 0x86, 0xac, 0xf4, 0x8b, 0x44, 0x70, 0x5d, 0xf0, 0x63,
-		0x19, 0xe7, 0x31, 0xd3, 0x92, 0x21, 0x57, 0xce, 0x44, 0x91, 0x32, 0x7e,
-		0x98, 0x86, 0x22, 0x61, 0x33, 0x18, 0xa0, 0xe0, 0x73, 0xa1, 0x74, 0x19,
-		0x4e, 0xe0, 0x79, 0xe1, 0x78, 0x14, 0xe0, 0x36, 0x32, 0xcd, 0x15, 0xb8,
-		0x8e, 0x87, 0x8e, 0x3b, 0x6a, 0xe0, 0x3b, 0x90, 0x5c, 0xb1, 0x4c, 0x6a,
-		0x86, 0x9c, 0x2a, 0x57, 0xd5, 0x61, 0x5c, 0x40, 0x81, 0x60, 0xac, 0xc4,
-		0xa0, 0x89, 0x89, 0x8c, 0x28, 0x92, 0xcb, 0x4c, 0x97, 0x56, 0xdd, 0x64,
-		0x0f, 0xc3, 0xde, 0x85, 0xc7, 0x4f, 0xc6, 0x0d, 0xa6, 0x46, 0xc4, 0x14,
-		0x91, 0xc7, 0x94, 0x98, 0x9b, 0x7c, 0x04, 0x51, 0x1f, 0x05, 0x5f, 0x81,
-		0xed, 0x5a, 0x2f, 0x44, 0x36, 0x6f, 0x43, 0xb2, 0xf7, 0xd7, 0x8e, 0x6b,
-		0x07, 0x9e, 0x73, 0x31, 0x06, 0xfc, 0x37, 0x8e, 0xfd, 0xa1, 0xc1, 0xa1,
-		0x1f, 0x46, 0x88, 0xb3, 0xf0, 0x11, 0x6e, 0x03, 0x59, 0x14, 0xcb, 0x45,
-		0xa4, 0x97, 0x05, 0xb7, 0xec, 0xb1, 0xb9, 0xb7, 0xdf, 0xeb, 0x5f, 0xda,
-		0x41, 0xef, 0x06, 0xc6, 0x77, 0x1b, 0xa7, 0x46, 0x84, 0x01, 0x94, 0x11,
-		0x33, 0x11, 0x95, 0xfa, 0x57, 0xf4, 0xe3, 0x89, 0xef, 0x0c, 0x3f, 0x06,
-		0x84, 0xc1, 0x86, 0xdc, 0xfa, 0x44, 0x90, 0x51, 0x16, 0x2f, 0x89, 0x06,
-		0x0d, 0x46, 0xe7, 0xcb, 0xd9, 0xcc, 0xe4, 0x87, 0x6c, 0x8e, 0x48, 0x47,
-		0x82, 0x88, 0x50, 0x89, 0x32, 0x9e, 0x1c, 0xb0, 0x7b, 0xce, 0x73, 0x2a,
-		0x48, 0x90, 0x49, 0x98, 0x7c, 0x50, 0x55, 0xa6, 0x58, 0x66, 0x3f, 0x68,
-		0x76, 0x9f, 0x01, 0xc3, 0x15, 0x55, 0x44, 0xb3, 0xd9, 0x86, 0x4b, 0x8e,
-		0x07, 0xc1, 0xf9, 0xf5, 0x70, 0x48, 0x39, 0xd6, 0x26, 0x8c, 0x8e, 0xc8,
-		0x86, 0x63, 0xaa, 0x9c, 0x88, 0x1b, 0x24, 0x9d, 0x35, 0x0c, 0x09, 0x80,
-		0x8c, 0xf9, 0xca, 0x92, 0xe9, 0x5d, 0x9f, 0xff, 0xce, 0xee, 0xfb, 0xa6,
-		0x60, 0xd4, 0xe5, 0xf3, 0x85, 0xaa, 0xd5, 0x2b, 0x4b, 0x0f, 0x25, 0x69,
-		0x3a, 0x72, 0xca, 0x54, 0xaa, 0xf3, 0xf6, 0x9c, 0x7e, 0x53, 0x72, 0x3c,
-		0x7d, 0xf5, 0xe6, 0x35, 0xf6, 0xde, 0xbf, 0xaf, 0x36, 0x1e, 0x1e, 0xcc,
-		0xea, 0xf1, 0xab, 0x3a, 0x57, 0xd4, 0x6c, 0x66, 0x85, 0x4c, 0x61, 0xe0,
-		0x18, 0xf1, 0xaf, 0xac, 0xa1, 0x3b, 0x19, 0x3d, 0xed, 0x41, 0xf1, 0xa5,
-		0xf1, 0x31, 0x12, 0x92, 0xfc, 0x20, 0x0f, 0x95, 0x5a, 0xc9, 0x22, 0xae,
-		0xb3, 0xc9, 0x26, 0x93, 0x50, 0x66, 0x93, 0xe1, 0x52, 0x2f, 0xbe, 0xc4,
-		0xb0, 0xda, 0x68, 0xa3, 0x34, 0x2f, 0x96, 0x77, 0x5f, 0xee, 0xf7, 0xaf,
-		0x1c, 0x7b, 0xec, 0x07, 0x8e, 0xe1, 0x52, 0x7d, 0x94, 0xf1, 0x5b, 0x16,
-		0xdd, 0xc9, 0xd4, 0xb8, 0xbc, 0xc9, 0xdb, 0xa8, 0x95, 0x61, 0x2e, 0x2a,
-		0x56, 0xa4, 0x4f, 0x87, 0xe4, 0xb3, 0x7a, 0xd7, 0xfe, 0x65, 0x55, 0x59,
-		0x6b, 0xb2, 0x06, 0x89, 0x89, 0xf4, 0x8e, 0x11, 0xa2, 0x43, 0xff, 0xc8,
-		0x42, 0xfc, 0x89, 0x5b, 0xfe, 0xe4, 0x9d, 0x3d, 0xfe, 0xc6, 0x43, 0x51,
-		0x04, 0x6c, 0x02, 0x2d, 0xef, 0x79, 0x66, 0x99, 0xa2, 0xa8, 0x59, 0x94,
-		0x08, 0x8e, 0x18, 0x10, 0x71, 0x59, 0x28, 0x38, 0x62, 0x43, 0x1b, 0x28,
-		0xb1, 0x5f, 0xb3, 0x43, 0x48, 0x2a, 0x89, 0x52, 0x15, 0x53, 0x71, 0x91,
-		0x28, 0x33, 0x0a, 0xa5, 0x4e, 0xce, 0xcb, 0xe2, 0xd5, 0x41, 0x5d, 0xfe,
-		0x23, 0x8f, 0xf4, 0x06, 0x1e, 0xb3, 0xf3, 0x1f, 0xc3, 0xb3, 0x5a, 0xad,
-		0x2a, 0x56, 0x00, 0x4a, 0x99, 0x8b, 0x8c, 0x0e, 0x84, 0x93, 0xc8, 0x66,
-		0xb2, 0xcd, 0x8d, 0x7f, 0x7d, 0x33, 0x39, 0xa4, 0xa4, 0xf2, 0xb7, 0x0f,
-		0xe2, 0x2a, 0x0f, 0x6c, 0x29, 0x25, 0x4b, 0xc8, 0x8e, 0x0d, 0x97, 0xbd,
-		0x18, 0x7f, 0xf5, 0x54, 0x05, 0x71, 0x05, 0xc9, 0xc3, 0xc3, 0xbf, 0x0d,
-		0x07, 0x72, 0x98, 0x71, 0x7e, 0xf6, 0xf7, 0xbf, 0xfd, 0xe5, 0x1f, 0x7f,
-		0xfe, 0x2b, 0x25, 0xfd, 0x3d, 0x3e, 0x52, 0x84, 0xf9, 0xa2, 0x0a, 0x8c,
-		0x4a, 0x82, 0x76, 0xb7, 0xe1, 0x22, 0x67, 0x6c, 0xaf, 0x93, 0xec, 0x3d,
-		0x55, 0x4a, 0x8e, 0x13, 0x3c, 0x8b, 0xc8, 0x31, 0x56, 0x5c, 0xdc, 0xc9,
-		0x7d, 0xa8, 0xc1, 0x0f, 0xb2, 0xb6, 0xae, 0xcf, 0x47, 0x73, 0x71, 0x78,
-		0x57, 0x3b, 0xda, 0xf1, 0xaf, 0xb8, 0xe7, 0xd7, 0x8f, 0x6e, 0x39, 0x69,
-		0x85, 0xa0, 0x5e, 0x09, 0xad, 0xf7, 0x25, 0xb6, 0x7f, 0x01, 0xc6, 0x7d,
-		0x96, 0x47, 0x0c, 0x56, 0xac, 0x9f, 0x50, 0xf8, 0x15, 0xe1, 0x7f, 0xe1,
-		0xcc, 0x3e, 0xa9, 0x0d, 0x76, 0xff, 0x0b, 0x99, 0x0d, 0xe3, 0x86, 0xdd,
-		0xbe, 0x41, 0xe4, 0x2f, 0x8f, 0x6c, 0x4b, 0x1c, 0x51, 0x79, 0xda, 0xea,
-		0xe5, 0x78, 0x8a, 0xa9, 0xa1, 0x6c, 0x99, 0x90, 0xd7, 0xf1, 0x43, 0x96,
-		0xab, 0x86, 0x72, 0x67, 0xf8, 0xa8, 0x88, 0xad, 0xde, 0xa0, 0x37, 0xf5,
-		0x4d, 0x46, 0x2d, 0x57, 0xea, 0x0e, 0xaa, 0xda, 0xaf, 0xda, 0xb2, 0x8b,
-		0x3e, 0xea, 0x03, 0xf0, 0x7b, 0x0c, 0x13, 0x2a, 0x14, 0x48, 0x3a, 0x32,
-		0x8b, 0xd5, 0x16, 0xc7, 0x93, 0x2e, 0xda, 0x27, 0x70, 0xba, 0xe9, 0x91,
-		0x22, 0x27, 0xdd, 0x9a, 0x51, 0x29, 0x8b, 0xc9, 0x55, 0x4d, 0x59, 0xc0,
-		0x20, 0x43, 0x0e, 0x42, 0x7d, 0x64, 0xd4, 0x5c, 0x6f, 0xca, 0xc0, 0x19,
-		0x33, 0x07, 0x4e, 0x59, 0xeb, 0xf4, 0xa4, 0xfb, 0xf2, 0xc7, 0x16, 0x16,
-		0xea, 0x53, 0x58, 0x7b, 0xea, 0x32, 0x8f, 0x8e, 0x8e, 0x8f, 0x8e, 0x5a,
-		0x55, 0x45, 0x31, 0x0d, 0x8e, 0x52, 0x60, 0xb6, 0x1f, 0x0f, 0xca, 0x23,
-		0x4f, 0xb8, 0x94, 0xb0, 0x54, 0x8d, 0xef, 0x3e, 0x4c, 0x30, 0x21, 0xdd,
-		0x38, 0x03, 0x03, 0x8a, 0xc9, 0x40, 0x67, 0x6c, 0x5a, 0xc8, 0x47, 0x11,
-		0x83, 0xa9, 0xe9, 0x75, 0xe6, 0x4c, 0xe6, 0x24, 0xb9, 0x2a, 0x85, 0xc3,
-		0x99, 0x53, 0xd3, 0xbe, 0x2c, 0xc2, 0x47, 0x2a, 0x56, 0xeb, 0x9a, 0x6a,
-		0xcd, 0x69, 0x24, 0x24, 0x16, 0xa8, 0x84, 0xa5, 0x7c, 0x4f, 0x1d, 0x3d,
-		0x7a, 0xdd, 0xf6, 0xbc, 0x8d, 0x4e, 0x97, 0xba, 0xd2, 0x6a, 0x57, 0xb5,
-		0x9e, 0xf4, 0xaf, 0x78, 0x24, 0xe2, 0x9e, 0x97, 0x4b, 0x55, 0xd5, 0x35,
-		0x48, 0x1d, 0xb0, 0x5c, 0xca, 0xc4, 0x83, 0xfb, 0x1c, 0x6c, 0x2a, 0x63,
-		0xcd, 0xf0, 0x09, 0xa3, 0x93, 0x97, 0xaf, 0x7f, 0x3c, 0x38, 0xea, 0x76,
-		0x0f, 0x42, 0x8c, 0x13, 0x9f, 0x05, 0x37, 0x60, 0x92, 0xde, 0xa7, 0xe8,
-		0x10, 0x0f, 0xf1, 0xf7, 0x30, 0x2e, 0x04, 0x58, 0x76, 0xcc, 0x22, 0x8b,
-		0x55, 0x56, 0xdf, 0x8a, 0xde, 0x0d, 0x0d, 0x52, 0xcd, 0x91, 0x3a, 0xf7,
-		0xd3, 0xfa, 0x9a, 0x9f, 0x6b, 0x61, 0x03, 0x6d, 0x3a, 0xf4, 0x0d, 0x5a,
-		0x65, 0x63, 0x77, 0x51, 0x37, 0xda, 0xb5, 0x4a, 0xb8, 0xd3, 0xab, 0x74,
-		0x8f, 0xa4, 0xbc, 0x17, 0xdc, 0xd4, 0xf4, 0xba, 0x73, 0xad, 0x1a, 0x56,
-		0x11, 0x90, 0x9e, 0x01, 0xfa, 0x56, 0xa1, 0xe9, 0x84, 0x53, 0x36, 0x34,
-		0xa8, 0x05, 0x1b, 0xe0, 0xe0, 0x76, 0x26, 0x3a, 0x2a, 0x8f, 0x6c, 0xd8,
-		0xad, 0x8a, 0xd1, 0x92, 0x21, 0xc2, 0xf2, 0xda, 0xb5, 0x1b, 0x6d, 0x94,
-		0x9d, 0x99, 0xc1, 0x54, 0x51, 0xe5, 0x34, 0xf7, 0x6f, 0x9d, 0xa5, 0xc9,
-		0xaf, 0x6e, 0xd0, 0xa8, 0xf3, 0x2d, 0xb9, 0xe0, 0xb8, 0xd9, 0x78, 0x12,
-		0x1d, 0x01, 0xa0, 0x05, 0x5a, 0x91, 0x3a, 0x0a, 0xb6, 0x98, 0xbc, 0x39,
-		0xf9, 0x2d, 0x46, 0xe2, 0x8b, 0x7e, 0x50, 0x07, 0x40, 0xe0, 0x3b, 0x46,
-		0xad, 0x72, 0xe3, 0x89, 0x4b, 0x22, 0x66, 0xdc, 0xf0, 0xd9, 0x73, 0xdc,
-		0xb3, 0x3d, 0x0f, 0x1d, 0x2c, 0xfa, 0xed, 0xa1, 0xbd, 0x7b, 0x7e, 0x83,
-		0x41, 0x0c, 0x1f, 0x53, 0x0b, 0x36, 0x5b, 0x66, 0xd1, 0xc1, 0xc6, 0xcf,
-		0xd5, 0x22, 0x3c, 0x22, 0xef, 0xc6, 0xdf, 0xe3, 0x57, 0x27, 0x95, 0x7b,
-		0xc7, 0xaf, 0x5a, 0xcd, 0x3b, 0x88, 0x66, 0x73, 0x85, 0x33, 0x08, 0x2e,
-		0x7b, 0xde, 0xe5, 0xf0, 0x7a, 0xdc, 0xc7, 0x25, 0x66, 0xeb, 0x49, 0x46,
-		0x73, 0x01, 0x86, 0xd4, 0x2d, 0x11, 0xc9, 0x10, 0x05, 0x42, 0x18, 0xfd,
-		0x5a, 0xe9, 0x1a, 0xbb, 0xbc, 0xcc, 0xbc, 0x83, 0x30, 0xac, 0x7a, 0x64,
-		0x0a, 0x43, 0x9f, 0x86, 0xd4, 0x24, 0x8c, 0x38, 0x35, 0xde, 0xd5, 0xba,
-		0x71, 0x8d, 0xa7, 0x29, 0xaf, 0xf4, 0xe8, 0x52, 0xe2, 0x07, 0x91, 0x89,
-		0xe5, 0x4e, 0x40, 0x56, 0xfb, 0xb8, 0xcc, 0xbd, 0x71, 0xfa, 0x84, 0x48,
-		0xd5, 0x79, 0xd6, 0xbd, 0xff, 0x85, 0xbb, 0xd3, 0x7f, 0x5b, 0x9f, 0xd0,
-		0x3e, 0x95, 0x0f, 0x27, 0xd5, 0xe4, 0xdb, 0x48, 0x08, 0x55, 0x57, 0xd4,
-		0xcc, 0x08, 0x94, 0x86, 0x0c, 0x76, 0x68, 0x54, 0x4b, 0x39, 0xea, 0x29,
-		0x79, 0x47, 0x94, 0xfa, 0x6c, 0x39, 0x59, 0xc0, 0x95, 0xd2, 0x34, 0x24,
-		0xc5, 0x14, 0xcf, 0x43, 0xf3, 0x4c, 0x91, 0x82, 0x52, 0xe4, 0xf0, 0x34,
-		0x7a, 0xef, 0x50, 0x75, 0xe8, 0x54, 0xc7, 0x0e, 0x4c, 0xdc, 0xb7, 0xac,
-		0x6a, 0x5a, 0xad, 0x56, 0xff, 0x9b, 0x4d, 0xfe, 0x4e, 0x7f, 0xdf, 0x35,
-		0x7e, 0x53, 0x2b, 0xee, 0x17, 0x30, 0x03, 0xa9, 0x39, 0xe0, 0x77, 0xcb,
-		0x39, 0xfd, 0x70, 0xd0, 0x61, 0xd1, 0xdf, 0x0f, 0x61, 0x61, 0xf4, 0xb7,
-		0x8b, 0x42, 0x16, 0xf4, 0xa3, 0x8f, 0x49, 0x18, 0x83, 0xcb, 0x6e, 0x6a,
-		0x2c, 0x39, 0x58, 0x57, 0xf6, 0x8d, 0x4d, 0xe9, 0xdd, 0x7c, 0x5a, 0x75,
-		0x8a, 0xaf, 0xb1, 0x31, 0xaa, 0x97, 0xc3, 0x19, 0x99, 0xa1, 0x5d, 0xad,
-		0xdf, 0x6e, 0x8e, 0x6d, 0x4e, 0x18, 0x34, 0x76, 0xc9, 0x69, 0xb1, 0x41,
-		0x4b, 0x8f, 0x27, 0x75, 0x7e, 0xc0, 0x76, 0x39, 0xb9, 0xe3, 0x87, 0x71,
-		0x2d, 0x7a, 0xed, 0x30, 0x81, 0xad, 0x18, 0x8a, 0xa3, 0x4c, 0x61, 0x81,
-		0x98, 0xa8, 0x58, 0x21, 0x35, 0x7e, 0x3f, 0x57, 0xa8, 0xf7, 0x91, 0x01,
-		0x74, 0x26, 0x69, 0xa8, 0x84, 0xcb, 0xd6, 0x49, 0xfb, 0xc5, 0x97, 0x09,
-		0x00, 0xd3, 0x77, 0xe0, 0x4e, 0xfc, 0x9e, 0xdf, 0x88, 0xfc, 0x51, 0xf8,
-		0x19, 0xf1, 0x9a, 0x21, 0x5d, 0x2d, 0xcd, 0x98, 0x0e, 0x56, 0x0a, 0x5c,
-		0x60, 0x60, 0x92, 0x73, 0x8b, 0x87, 0x81, 0x1b, 0x80, 0x8f, 0x7a, 0xbf,
-		0x0f, 0xe8, 0x95, 0xcb, 0xab, 0x4d, 0x60, 0x8c, 0x40, 0x8c, 0x14, 0x32,
-		0x35, 0x02, 0x4d, 0xcc, 0xf4, 0xd7, 0xf8, 0x1c, 0xbf, 0x41, 0x39, 0x09,
-		0x33, 0x30, 0x64, 0x3f, 0xfd, 0x84, 0xaf, 0x03, 0x86, 0x78, 0x1e, 0x9d,
-		0x1b, 0xbe, 0x9e, 0xf3, 0x07, 0x64, 0xa8, 0x4b, 0x67, 0x68, 0x9e, 0xdc,
-		0xde, 0x98, 0x80, 0x9d, 0xa7, 0xd4, 0xef, 0x91, 0xd6, 0x31, 0x3a, 0xeb,
-		0xf5, 0x97, 0x7a, 0x0d, 0x30, 0x6b, 0x7e, 0xfc, 0x42, 0x33, 0xfb, 0x73,
-		0x2e, 0x50, 0x51, 0xcc, 0xc3, 0x03, 0x89, 0x43, 0x0c, 0x48, 0x96, 0xe7,
-		0x31, 0x4f, 0x38, 0x4d, 0xd9, 0x33, 0x1a, 0xbe, 0x53, 0x88, 0x4d, 0x14,
-		0xdb, 0x70, 0xbd, 0x36, 0xc2, 0x6c, 0x9e, 0x27, 0x1a, 0x1e, 0x90, 0xed,
-		0x33, 0x7f, 0xd6, 0xb0, 0xe7, 0x19, 0x73, 0x79, 0x55, 0xf5, 0xcb, 0x92,
-		0x4f, 0x0f, 0x05, 0xe5, 0x5b, 0x6d, 0x05, 0x48, 0x8a, 0x14, 0x14, 0xce,
-		0xf9, 0x9e, 0xe4, 0xee, 0xda, 0x28, 0x2e, 0x63, 0x0c, 0xa4, 0x01, 0x52,
-		0xce, 0xc8, 0x6b, 0xbe, 0x13, 0xfa, 0x38, 0x8f, 0x38, 0x2c, 0x36, 0xbc,
-		0x57, 0x0b, 0x9e, 0x35, 0xdb, 0x0b, 0x30, 0x49, 0x70, 0xdd, 0xd7, 0xb8,
-		0x36, 0xcb, 0x45, 0x15, 0x32, 0x3a, 0xca, 0x29, 0x1c, 0x96, 0x99, 0xf8,
-		0x5c, 0xe6, 0x85, 0x65, 0x9c, 0xef, 0xc4, 0x04, 0x91, 0x34, 0x5f, 0x5f,
-		0xf1, 0x0d, 0x06, 0x97, 0xcd, 0x6e, 0xa6, 0x7e, 0x3f, 0xdd, 0xbc, 0x4b,
-		0x99, 0x34, 0xb3, 0x83, 0x13, 0x2d, 0x6e, 0xe1, 0xf4, 0xb5, 0xc9, 0x7c,
-		0x5b, 0x84, 0x81, 0x08, 0xe7, 0x19, 0x2e, 0x14, 0x51, 0x0d, 0x5e, 0x39,
-		0x54, 0x9b, 0x34, 0xd9, 0x6a, 0x4c, 0xf1, 0x5f, 0x25, 0xdc, 0x19, 0xeb,
-		0xb7, 0xa7, 0xf4, 0x6f, 0x9f, 0xc4, 0x4b, 0x0b, 0x73, 0xea, 0x28, 0x90,
-		0xff, 0xa2, 0x30, 0x63, 0x77, 0xa4, 0x26, 0x27, 0xf8, 0xd0, 0x24, 0xf1,
-		0x2a, 0x27, 0x7e, 0x6a, 0x1d, 0xfd, 0xdc, 0x78, 0x4a, 0x6d, 0x1d, 0xb4,
-		0x8e, 0xb7, 0xbe, 0x6f, 0xc9, 0x2e, 0xb6, 0x73, 0x63, 0xbb, 0x5e, 0x13,
-		0xba, 0x4d, 0x5e, 0xde, 0x85, 0xef, 0xe9, 0x59, 0x73, 0x03, 0xe1, 0xc0,
-		0xa5, 0xe3, 0xa6, 0x59, 0x87, 0x81, 0xe9, 0xef, 0x3f, 0x03, 0x00, 0x00,
-		0xff, 0xff, 0x77, 0x1f, 0xe2, 0xa5, 0x2d, 0x18, 0x00, 0x00,
-		},
+		0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xb4, 0x59,
+		0xeb, 0x72, 0xdb, 0xc8, 0x95, 0xfe, 0x8f, 0xa7, 0x68, 0x73, 0x67, 0x76,
+		0xec, 0x2d, 0x89, 0xa4, 0xe4, 0xb5, 0xec, 0x91, 0xc7, 0xb5, 0xa6, 0x48,
+		0x50, 0xc2, 0x9a, 0x17, 0x0d, 0x00, 0xc9, 0xa3, 0xb8, 0x54, 0x28, 0x08,
+		0x68, 0x92, 0x1d, 0x01, 0x68, 0x08, 0xdd, 0x14, 0xc5, 0xfc, 0xcb, 0x2b,
+		0xa4, 0xf2, 0x34, 0x79, 0x9e, 0xfc, 0xc8, 0x63, 0xe4, 0x3b, 0x0d, 0x80,
+		0x02, 0x65, 0x8e, 0xc6, 0xb9, 0x55, 0x52, 0x16, 0xd1, 0xdd, 0xe7, 0xf4,
+		0xb9, 0x7c, 0xe7, 0xd6, 0xf3, 0x9e, 0xf5, 0xf2, 0x9c, 0x65, 0x61, 0xca,
+		0x99, 0x5e, 0x84, 0x9a, 0xa9, 0x85, 0x5c, 0x29, 0x26, 0x33, 0xc6, 0xef,
+		0x79, 0xb1, 0x66, 0x79, 0x38, 0xc7, 0x86, 0xd0, 0x09, 0xb7, 0x7a, 0xe7,
+		0xe7, 0xc1, 0xa4, 0x37, 0xb6, 0xd9, 0x07, 0x76, 0x2a, 0xe7, 0xea, 0x18,
+		0xff, 0xb2, 0x53, 0xa1, 0x99, 0xc7, 0x8b, 0x7b, 0x11, 0x95, 0xfb, 0xa3,
+		0xe9, 0xe9, 0x14, 0xfb, 0x22, 0x9d, 0x77, 0x66, 0x21, 0x56, 0x65, 0xd6,
+		0xce, 0xb3, 0xb9, 0xf5, 0x9e, 0xf5, 0x17, 0x61, 0x06, 0x4e, 0x38, 0x2e,
+		0x66, 0x6c, 0x2d, 0x97, 0xac, 0x58, 0x66, 0x2c, 0x91, 0x51, 0x98, 0x24,
+		0x6b, 0xcb, 0xbd, 0x98, 0x04, 0x17, 0x9e, 0xed, 0x82, 0x72, 0x2e, 0x34,
+		0x4e, 0xdb, 0x42, 0x2f, 0x78, 0xc1, 0x5a, 0x31, 0xbf, 0x6f, 0xed, 0xb1,
+		0x56, 0x5e, 0xc8, 0xb8, 0xc5, 0x24, 0x16, 0x34, 0x57, 0x1a, 0x2b, 0x31,
+		0x9f, 0x85, 0xcb, 0x04, 0xbc, 0x54, 0x79, 0xc6, 0x70, 0x18, 0x4f, 0x07,
+		0x24, 0x1b, 0xbe, 0x2d, 0xeb, 0x4b, 0xc1, 0x73, 0xa9, 0x84, 0x96, 0xc5,
+		0xfa, 0xda, 0x72, 0xa7, 0x53, 0x1f, 0x1b, 0x96, 0xd7, 0x77, 0x9d, 0x73,
+		0x3f, 0xf0, 0xaf, 0xce, 0xe9, 0xdc, 0x4d, 0xa8, 0x16, 0x38, 0xa8, 0x20,
+		0x3d, 0x2f, 0xae, 0xad, 0x73, 0x77, 0xea, 0x4f, 0xfb, 0xd3, 0x11, 0x76,
+		0x16, 0x5a, 0xe7, 0xd6, 0x60, 0x3a, 0xee, 0x39, 0x13, 0x7c, 0x19, 0x21,
+		0x17, 0x52, 0x69, 0xc3, 0x27, 0xb8, 0x70, 0xe9, 0xc8, 0xf7, 0x2f, 0xeb,
+		0xf3, 0xaf, 0xd4, 0x71, 0xa7, 0xf3, 0xfd, 0xcb, 0xf2, 0x38, 0x3e, 0xbe,
+		0x7f, 0x79, 0xe6, 0xfb, 0xe7, 0xc1, 0xf9, 0xd4, 0xf5, 0x5f, 0xa9, 0x8e,
+		0x65, 0x3e, 0x7a, 0x83, 0x01, 0xe9, 0x66, 0x6d, 0x76, 0xf0, 0xf1, 0xba,
+		0xdb, 0xed, 0x5a, 0x9e, 0x77, 0x56, 0x7f, 0x1f, 0x1e, 0x42, 0xef, 0x81,
+		0x50, 0xe1, 0x4d, 0xc2, 0x59, 0x7f, 0x30, 0x21, 0xfb, 0x67, 0x4c, 0x64,
+		0xb5, 0xf6, 0xa9, 0x8c, 0xb9, 0x35, 0x1d, 0x0e, 0x47, 0xce, 0xc4, 0xae,
+		0x55, 0x9d, 0x85, 0x89, 0xe2, 0xd6, 0xc0, 0xf1, 0x7a, 0x27, 0x23, 0x3b,
+		0x70, 0xa7, 0x17, 0xbe, 0xed, 0x92, 0x0b, 0x36, 0x5b, 0xef, 0xd9, 0x29,
+		0xcf, 0x78, 0x11, 0x6a, 0xce, 0x94, 0xe6, 0xb9, 0x3a, 0xc6, 0xca, 0x77,
+		0x2c, 0x8a, 0xe1, 0x56, 0xbd, 0xe8, 0x68, 0xd9, 0x99, 0xc3, 0x91, 0x9d,
+		0x68, 0xa9, 0xb4, 0x4c, 0x3b, 0xa4, 0xb6, 0x32, 0x07, 0xe6, 0xd2, 0xb8,
+		0xe7, 0xbb, 0xd3, 0x29, 0xa9, 0xdc, 0x51, 0x45, 0xd4, 0xc9, 0x6f, 0xe7,
+		0x9d, 0xa8, 0x58, 0xe7, 0xa0, 0xd1, 0x89, 0xea, 0xcc, 0x2b, 0xb6, 0x41,
+		0xc4, 0x0b, 0xdd, 0xc6, 0xf9, 0xfd, 0x28, 0xfc, 0xa0, 0x8b, 0x25, 0x67,
+		0xfb, 0xf1, 0x12, 0x1b, 0x42, 0x66, 0x1f, 0xde, 0xbd, 0x3d, 0xea, 0x2e,
+		0xba, 0x69, 0x57, 0xb1, 0x7d, 0x32, 0xdf, 0x87, 0x74, 0x4d, 0x7f, 0xda,
+		0xfc, 0x21, 0x4c, 0xf3, 0x84, 0xb7, 0x23, 0x99, 0x5a, 0x7d, 0xdb, 0xf5,
+		0x83, 0xa1, 0x33, 0x22, 0x65, 0x9a, 0x52, 0x74, 0x0c, 0xdb, 0x9c, 0xa7,
+		0xd6, 0x27, 0xfb, 0x6a, 0xe7, 0x81, 0x5b, 0xbe, 0x36, 0xfb, 0xef, 0xd9,
+		0x45, 0x9e, 0x03, 0x2a, 0x09, 0xcc, 0x95, 0x30, 0x39, 0x63, 0x9a, 0x83,
+		0x3b, 0x29, 0x1c, 0x66, 0x31, 0x94, 0x86, 0x28, 0x11, 0x9b, 0x09, 0xd8,
+		0x94, 0x54, 0xc6, 0xf1, 0x06, 0x74, 0x80, 0x31, 0xb3, 0xca, 0x56, 0x00,
+		0x1b, 0x37, 0xa0, 0xa6, 0x65, 0xfe, 0xc0, 0xa3, 0xa5, 0xe6, 0xb1, 0xe5,
+		0xf9, 0x3d, 0xdf, 0xe9, 0x07, 0xc6, 0xed, 0xe7, 0x3d, 0xff, 0x8c, 0x5c,
+		0x68, 0x7d, 0x89, 0x43, 0x1d, 0x02, 0x3b, 0xfc, 0xba, 0x81, 0xd3, 0x74,
+		0xad, 0xee, 0x12, 0x83, 0x54, 0x68, 0x38, 0x2f, 0xb8, 0x2a, 0xd1, 0x8a,
+		0x45, 0xa1, 0xf9, 0x6b, 0x6c, 0x08, 0xfd, 0x83, 0x22, 0xd8, 0x17, 0x2c,
+		0x5a, 0x48, 0x0a, 0x96, 0xc1, 0x49, 0x8d, 0x43, 0x43, 0x6b, 0x9d, 0x4d,
+		0x3d, 0x42, 0xc1, 0xc1, 0xe1, 0xdb, 0x76, 0x17, 0xff, 0x3b, 0x38, 0x7e,
+		0xfd, 0xba, 0x7b, 0x64, 0x55, 0xe1, 0x46, 0x5e, 0xb2, 0xaa, 0x00, 0x29,
+		0xa4, 0xd4, 0xd6, 0x79, 0xcf, 0xf3, 0x3e, 0x0f, 0xd8, 0x07, 0x88, 0x30,
+		0xa4, 0x8b, 0x1a, 0xd7, 0x66, 0xc9, 0x7a, 0x8f, 0xf1, 0x3a, 0x7e, 0x4a,
+		0x3c, 0x91, 0x64, 0x05, 0xbf, 0x5b, 0x8a, 0x82, 0x97, 0x82, 0x01, 0xf1,
+		0x62, 0xb6, 0xde, 0x9f, 0x2d, 0x93, 0xa4, 0x05, 0x10, 0x8e, 0x36, 0xb1,
+		0x53, 0x9e, 0xaf, 0xd9, 0xd6, 0xf2, 0x1b, 0xae, 0x56, 0x65, 0x02, 0xd2,
+		0xdf, 0xe0, 0xa6, 0x1d, 0xdf, 0xc0, 0x1c, 0x61, 0x9c, 0x8a, 0xec, 0xda,
+		0x04, 0x52, 0xb4, 0x2c, 0x84, 0x46, 0xbc, 0x39, 0x13, 0x58, 0x6e, 0x34,
+		0x02, 0x12, 0xfb, 0x9f, 0x1a, 0x50, 0x7c, 0xf1, 0xa2, 0x7f, 0xd6, 0x9b,
+		0x9c, 0xda, 0xcc, 0x3f, 0x73, 0x3c, 0xe6, 0x4f, 0xd9, 0x27, 0xdb, 0x3e,
+		0x67, 0x57, 0xd3, 0x0b, 0x97, 0x19, 0xdd, 0x06, 0x3d, 0xbf, 0xc7, 0xbc,
+		0xde, 0xd0, 0x7e, 0xf1, 0xc2, 0xf2, 0xec, 0xbe, 0x6b, 0xfb, 0x01, 0xbc,
+		0x0f, 0x06, 0x2f, 0xfe, 0xeb, 0xe3, 0x70, 0x60, 0x7f, 0x76, 0xf1, 0xff,
+		0xff, 0xfe, 0x9f, 0x97, 0xe0, 0xd4, 0x5b, 0x6a, 0xb9, 0x9f, 0xc8, 0x39,
+		0xa2, 0xa3, 0xe0, 0x29, 0x4f, 0x6f, 0xa0, 0x6b, 0x1c, 0xae, 0x95, 0x05,
+		0xec, 0x3b, 0x93, 0xc0, 0xb5, 0xc7, 0xf6, 0xf8, 0x04, 0xa1, 0x30, 0xe8,
+		0x5d, 0x79, 0xa0, 0x7f, 0x6b, 0xf5, 0xa7, 0xd3, 0x4f, 0x8e, 0x6d, 0x72,
+		0x4c, 0xc3, 0xa4, 0x41, 0xb8, 0xe2, 0x4a, 0xa6, 0xbc, 0xde, 0xde, 0xd0,
+		0x35, 0xcf, 0x88, 0x2c, 0x2a, 0x78, 0x2c, 0x4a, 0xab, 0xb8, 0x94, 0x14,
+		0x15, 0x50, 0x53, 0xc8, 0x87, 0x35, 0x0b, 0x97, 0xb0, 0x72, 0x06, 0x80,
+		0x19, 0xbc, 0xb3, 0x05, 0x0f, 0x63, 0x08, 0x62, 0x52, 0x29, 0x80, 0xb8,
+		0x54, 0xd5, 0x87, 0xe5, 0xda, 0x97, 0xb6, 0xeb, 0xd9, 0x01, 0x52, 0xc6,
+		0x2f, 0x57, 0x41, 0xef, 0xc2, 0x3f, 0xb3, 0x27, 0x00, 0x16, 0xc0, 0x35,
+		0xdd, 0xe4, 0xbd, 0x5f, 0xf6, 0x3f, 0xdb, 0x27, 0xb4, 0xb5, 0x4f, 0x0b,
+		0x55, 0x5e, 0x02, 0x50, 0xae, 0xad, 0x5e, 0xdf, 0x77, 0x2e, 0xed, 0xa0,
+		0x0f, 0x0f, 0x05, 0x23, 0xfa, 0x35, 0x76, 0x26, 0x08, 0x74, 0x52, 0xec,
+		0xe0, 0x5d, 0x17, 0xcc, 0x3d, 0x9b, 0xe0, 0x49, 0x80, 0xf8, 0xd5, 0x43,
+		0x88, 0x12, 0x23, 0x0d, 0xe7, 0x31, 0xd3, 0x92, 0x21, 0x2d, 0xcf, 0x44,
+		0x91, 0x32, 0xbe, 0x9f, 0x86, 0x22, 0x61, 0x33, 0xf8, 0xba, 0xe0, 0x73,
+		0xa1, 0x74, 0x19, 0xb9, 0xe0, 0x79, 0xea, 0x78, 0x94, 0x4b, 0x6c, 0x24,
+		0xb5, 0x11, 0xb8, 0x4e, 0x86, 0x8e, 0x3b, 0x6e, 0xb8, 0x72, 0x20, 0xb9,
+		0x62, 0x99, 0xd4, 0x0c, 0xe9, 0x5b, 0xae, 0x2a, 0x62, 0x5c, 0x40, 0x31,
+		0x67, 0x00, 0xc1, 0x60, 0x34, 0x13, 0x84, 0x51, 0x24, 0x97, 0x99, 0x2e,
+		0x01, 0xb4, 0x49, 0x54, 0x86, 0xbd, 0x6b, 0xf4, 0x6f, 0x30, 0x35, 0x22,
+		0xa6, 0x08, 0x72, 0xa6, 0xc4, 0xdc, 0xa4, 0x3e, 0x88, 0x7a, 0x2f, 0xf8,
+		0x0a, 0x6c, 0xd7, 0x7a, 0x21, 0xb2, 0x79, 0x1b, 0x92, 0xfd, 0x7c, 0xe1,
+		0xb8, 0x76, 0xe0, 0x39, 0xa7, 0x13, 0x78, 0xfa, 0xd2, 0xb1, 0x3f, 0x37,
+		0x38, 0xf4, 0xc3, 0x08, 0x21, 0x1d, 0xde, 0x03, 0xa1, 0x90, 0x45, 0xb1,
+		0x5c, 0x44, 0x7a, 0x59, 0x70, 0xcb, 0x9e, 0x98, 0x7b, 0xfb, 0xbd, 0xfe,
+		0x99, 0x1d, 0xf4, 0x2e, 0x81, 0x33, 0xb7, 0x41, 0x35, 0x26, 0x1b, 0x40,
+		0x19, 0x31, 0xab, 0x3c, 0x59, 0x9f, 0x9f, 0x4c, 0x7d, 0x67, 0x78, 0x15,
+		0x90, 0x0d, 0x9a, 0xc7, 0x25, 0x72, 0x45, 0xcc, 0x35, 0xa8, 0x8e, 0x4d,
+		0xa9, 0xa0, 0x02, 0x80, 0xb2, 0xb5, 0x58, 0xde, 0x50, 0x4e, 0xa3, 0xd0,
+		0x10, 0x5a, 0x95, 0x99, 0x55, 0x28, 0xb5, 0xe4, 0xaa, 0x73, 0x70, 0xf4,
+		0xa6, 0xe6, 0xf9, 0x1c, 0x16, 0x36, 0x97, 0x58, 0x5f, 0x56, 0xfc, 0x66,
+		0x21, 0xe5, 0x2d, 0xe5, 0x98, 0x7e, 0x01, 0x6c, 0xe9, 0x50, 0xdd, 0xc2,
+		0x22, 0xb0, 0xf1, 0x7d, 0x98, 0x90, 0x69, 0x60, 0x63, 0xe4, 0x28, 0x65,
+		0xf9, 0x3d, 0xef, 0x53, 0xe0, 0x4c, 0xe0, 0xac, 0xcb, 0x1e, 0x49, 0x79,
+		0x40, 0xde, 0xe1, 0x89, 0x00, 0x4e, 0x51, 0xb6, 0x53, 0x2e, 0x97, 0x9a,
+		0x8e, 0x23, 0x38, 0x65, 0x16, 0x2b, 0x6b, 0x60, 0x13, 0x3a, 0xdc, 0xc0,
+		0x77, 0xc6, 0x36, 0xca, 0x05, 0x08, 0xde, 0xe0, 0x36, 0x42, 0x01, 0xd5,
+		0xc0, 0x52, 0xc6, 0x41, 0x43, 0xd9, 0x93, 0xe5, 0x6c, 0x66, 0xb2, 0x6b,
+		0x36, 0x47, 0x9e, 0x04, 0xaa, 0x23, 0xd4, 0xf1, 0x8c, 0x27, 0x7b, 0xec,
+		0x96, 0xf3, 0x9c, 0xca, 0x39, 0xcc, 0x2c, 0x4c, 0x36, 0xad, 0xea, 0x7a,
+		0x2c, 0xb3, 0x1f, 0x34, 0xbb, 0xcd, 0x00, 0x8b, 0x15, 0xf5, 0x13, 0x66,
+		0xb3, 0x8d, 0x80, 0x9e, 0x0c, 0x82, 0x93, 0x8b, 0xe1, 0x90, 0x2a, 0x94,
+		0x4d, 0xaa, 0x1e, 0x10, 0x2c, 0x27, 0x14, 0x2c, 0xc8, 0x3a, 0x48, 0xd9,
+		0x6b, 0x60, 0x93, 0x14, 0x23, 0x6f, 0x94, 0x0d, 0x87, 0x77, 0x71, 0xf2,
+		0xff, 0x76, 0xdf, 0x37, 0xe5, 0xb6, 0x6e, 0x3e, 0x5e, 0xa9, 0xda, 0x63,
+		0x65, 0xe1, 0xa6, 0x12, 0x97, 0x1a, 0x57, 0xa8, 0x54, 0xe7, 0xed, 0x39,
+		0xfd, 0x26, 0x37, 0x1c, 0xbf, 0x79, 0xf7, 0x16, 0x7b, 0x3f, 0xff, 0x5c,
+		0x6d, 0xdc, 0xdd, 0x99, 0xd5, 0xc3, 0x37, 0x75, 0xa6, 0xad, 0xd9, 0xcc,
+		0x0a, 0x99, 0x02, 0xb3, 0x31, 0xb2, 0xa7, 0xb2, 0x86, 0xee, 0x74, 0xfc,
+		0xb8, 0x07, 0xc5, 0x37, 0x41, 0x6c, 0xa0, 0x9d, 0x87, 0x4a, 0xad, 0x64,
+		0x11, 0xd7, 0xb9, 0x78, 0x93, 0x87, 0xa9, 0x2e, 0x48, 0x4a, 0x07, 0x5f,
+		0xdb, 0xb0, 0xda, 0x68, 0x97, 0x08, 0xf9, 0x7a, 0xbf, 0x3f, 0x72, 0x80,
+		0x80, 0xc0, 0x31, 0x5c, 0xaa, 0x8f, 0x32, 0xfb, 0x95, 0x2d, 0xcb, 0xf4,
+		0xdc, 0x44, 0x71, 0x0d, 0xb4, 0x30, 0x17, 0xed, 0x06, 0xd8, 0x48, 0x3e,
+		0x8b, 0x50, 0x54, 0xf5, 0x25, 0x3b, 0xf0, 0x68, 0xf2, 0x64, 0xc7, 0x08,
+		0xd1, 0xa1, 0x7f, 0x64, 0x21, 0xfe, 0xc0, 0x2d, 0x7f, 0xfa, 0xc9, 0x9e,
+		0x7c, 0x23, 0x51, 0x14, 0xc1, 0x36, 0x81, 0x96, 0xb7, 0x3c, 0xb3, 0x4c,
+		0x4b, 0xa1, 0x59, 0x94, 0x08, 0x64, 0x3e, 0x26, 0xe2, 0xb2, 0xcc, 0x72,
+		0x84, 0xbb, 0x36, 0xa6, 0xc4, 0x7e, 0xcd, 0x0e, 0x88, 0x53, 0x12, 0x85,
+		0x3e, 0xa6, 0xd2, 0x2c, 0x51, 0xa4, 0x15, 0x1a, 0x05, 0x39, 0x2f, 0x4b,
+		0x7f, 0x07, 0x29, 0xf4, 0xf7, 0x3c, 0xd2, 0x1b, 0xf3, 0x98, 0x9d, 0x7f,
+		0xd9, 0x3c, 0xab, 0xd5, 0xaa, 0x62, 0x05, 0x43, 0x29, 0x73, 0x91, 0xd1,
+		0x81, 0xec, 0x24, 0xb2, 0x99, 0x6c, 0x73, 0x83, 0xaf, 0x6f, 0x3e, 0x0e,
+		0x29, 0xa9, 0x79, 0xd8, 0x65, 0xe2, 0x2a, 0xb5, 0x6d, 0x29, 0x25, 0x4b,
+		0x93, 0x1d, 0x1a, 0x2e, 0x3b, 0x6d, 0xfc, 0x2c, 0x55, 0x65, 0xe2, 0xca,
+		0x24, 0x77, 0x77, 0xff, 0xb4, 0x39, 0x90, 0x96, 0x0d, 0xf8, 0xd9, 0x5f,
+		0xff, 0xf2, 0xa7, 0xbf, 0xfd, 0xf1, 0xcf, 0x54, 0x32, 0x77, 0x60, 0xa4,
+		0x08, 0xf3, 0x45, 0x15, 0x18, 0x95, 0x04, 0xed, 0x6e, 0x03, 0x22, 0xef,
+		0xd9, 0x4e, 0x90, 0xec, 0xa4, 0x2a, 0x25, 0x07, 0x05, 0xcf, 0x22, 0x02,
+		0xc6, 0x8a, 0x8b, 0x1b, 0xb9, 0xcb, 0x6a, 0xc0, 0x41, 0xd6, 0xd6, 0x35,
+		0x7d, 0x34, 0x17, 0xfb, 0x37, 0x35, 0xd0, 0x0e, 0x7f, 0x03, 0x9e, 0xcf,
+		0x93, 0x6e, 0x81, 0xb4, 0xb2, 0xa0, 0x5e, 0x09, 0xad, 0x77, 0x25, 0xb6,
+		0x7f, 0xc0, 0x8c, 0xbb, 0x3c, 0x8f, 0x18, 0xac, 0x58, 0x3f, 0x5a, 0xe1,
+		0x37, 0x84, 0xff, 0x15, 0x9a, 0x5d, 0x52, 0x1b, 0xdb, 0xfd, 0x27, 0x64,
+		0x36, 0x8c, 0x1b, 0x7e, 0xfb, 0x06, 0x91, 0xbf, 0x26, 0xd9, 0x96, 0x38,
+		0xa2, 0x8a, 0xbb, 0xd5, 0x09, 0xf3, 0x14, 0x33, 0x57, 0xd9, 0x70, 0x22,
+		0xaf, 0xe3, 0x87, 0x2c, 0x57, 0xcd, 0xc9, 0x27, 0xa3, 0x5b, 0x75, 0xd8,
+		0xea, 0x0d, 0x7a, 0xe7, 0xbe, 0xc9, 0xa8, 0xe5, 0x4a, 0xdd, 0x7f, 0x56,
+		0xfb, 0x55, 0x53, 0x7b, 0xda, 0xdf, 0xaa, 0x80, 0x55, 0x49, 0xdb, 0xe2,
+		0x78, 0xd4, 0xb5, 0x1a, 0xb5, 0xf0, 0xa8, 0x5b, 0x33, 0x2a, 0x65, 0x31,
+		0xb9, 0xaa, 0x29, 0x0b, 0x18, 0x64, 0xc8, 0x41, 0xa6, 0x79, 0x43, 0x07,
+		0xbd, 0x29, 0x03, 0xef, 0x99, 0x21, 0x38, 0x66, 0xad, 0xe3, 0xa3, 0xee,
+		0xeb, 0x1f, 0x5b, 0x58, 0xa8, 0xa9, 0xb0, 0xf6, 0xd8, 0xa3, 0x1f, 0x1c,
+		0x1c, 0x1e, 0x1c, 0xb4, 0xaa, 0x8a, 0x62, 0x7a, 0x36, 0xa5, 0xc0, 0x6c,
+		0xb7, 0x3d, 0x28, 0x8f, 0x3c, 0xda, 0xa5, 0x34, 0x4b, 0x35, 0x36, 0xec,
+		0xb2, 0x09, 0x1a, 0x84, 0x4b, 0x67, 0x60, 0x8c, 0x62, 0x32, 0xd0, 0x7b,
+		0x76, 0x5e, 0xc8, 0x7b, 0x41, 0x1d, 0xa6, 0x69, 0xdf, 0xe6, 0x4c, 0xe6,
+		0x24, 0xb9, 0x2a, 0x85, 0x03, 0xcd, 0xb1, 0xe9, 0xc8, 0x16, 0xe1, 0x3d,
+		0x15, 0xab, 0x75, 0x7d, 0x6a, 0xcd, 0x69, 0xa0, 0x26, 0x16, 0xa8, 0x84,
+		0xa5, 0x7c, 0x8f, 0xf3, 0x10, 0x26, 0x85, 0xf6, 0xbc, 0x8d, 0x39, 0x81,
+		0x7a, 0xfa, 0x6a, 0x57, 0xb5, 0x1e, 0xf5, 0xaf, 0x78, 0x24, 0xe2, 0x96,
+		0x97, 0x4b, 0x55, 0xd5, 0x35, 0x96, 0xda, 0x63, 0xb9, 0x94, 0x89, 0x07,
+		0xf8, 0xec, 0x6d, 0x2a, 0x63, 0xcd, 0xf0, 0xd1, 0x46, 0x47, 0xaf, 0xdf,
+		0xfe, 0xb8, 0x77, 0xd0, 0xed, 0xee, 0x85, 0x18, 0xc6, 0x1e, 0x04, 0x37,
+		0xc6, 0x24, 0xbd, 0x8f, 0xd1, 0x5f, 0xef, 0xe3, 0xef, 0x7e, 0x5c, 0x50,
+		0xb7, 0xd2, 0x31, 0x8b, 0x2c, 0x56, 0x59, 0x7d, 0x2b, 0xda, 0x51, 0xf4,
+		0x7c, 0x35, 0x47, 0x9a, 0x7b, 0x8e, 0xeb, 0x6b, 0x3e, 0xd6, 0xc2, 0x06,
+		0xda, 0xcc, 0x37, 0x1b, 0x6b, 0x95, 0xbd, 0xea, 0x69, 0x3d, 0xa6, 0xd4,
+		0x2a, 0xe1, 0x4e, 0xaf, 0xd2, 0x3d, 0x42, 0x5b, 0x25, 0x78, 0xd9, 0x98,
+		0x57, 0x7d, 0x7f, 0xd5, 0xee, 0x8b, 0x80, 0xf4, 0x0c, 0xca, 0xfe, 0x0d,
+		0x14, 0x4e, 0xd9, 0xd0, 0xa0, 0x16, 0x6c, 0x0c, 0x07, 0xd8, 0x99, 0xe8,
+		0xa8, 0x10, 0xd9, 0xf0, 0x5b, 0x15, 0xa3, 0x25, 0x43, 0x84, 0xe5, 0x85,
+		0x6b, 0x37, 0xda, 0x28, 0x3b, 0x33, 0x63, 0xbd, 0xa2, 0xca, 0x69, 0xee,
+		0xdf, 0xa2, 0xa5, 0xb9, 0xb9, 0xee, 0x0f, 0xa9, 0x99, 0x2f, 0xb9, 0x80,
+		0xdc, 0x6c, 0x3c, 0x8a, 0x8e, 0x00, 0xa0, 0x96, 0x6e, 0x13, 0x05, 0x5b,
+		0x4c, 0xde, 0x1d, 0xfd, 0x6f, 0xb7, 0x6b, 0x9d, 0xf6, 0x37, 0xcd, 0xa0,
+		0xe9, 0xf1, 0xc0, 0xa4, 0xdc, 0x78, 0xe4, 0x92, 0x88, 0x19, 0x37, 0x7c,
+		0x76, 0x90, 0x7b, 0xb6, 0xe7, 0xd1, 0x50, 0x32, 0x72, 0x86, 0xf6, 0x53,
+		0xfa, 0x8d, 0x0d, 0x62, 0x60, 0x4c, 0x2d, 0xd8, 0x6c, 0x99, 0x45, 0x7b,
+		0x1b, 0x9c, 0xab, 0x45, 0x78, 0x40, 0xe8, 0xc6, 0xdf, 0xc3, 0x37, 0x47,
+		0x15, 0xbc, 0xe3, 0x37, 0xad, 0xe6, 0x1d, 0x74, 0x66, 0x73, 0x85, 0x33,
+		0x08, 0xce, 0x7a, 0xde, 0xd9, 0xf0, 0x62, 0xd2, 0xc7, 0x25, 0x66, 0xeb,
+		0x51, 0x46, 0x73, 0x01, 0x46, 0xfc, 0x2d, 0x11, 0xc9, 0x11, 0x05, 0x42,
+		0x18, 0xfd, 0x5a, 0x09, 0x8d, 0xa7, 0xbc, 0xcc, 0xb4, 0x88, 0x30, 0xac,
+		0xda, 0x7e, 0x0a, 0x43, 0x9f, 0x46, 0xfc, 0x24, 0x8c, 0x38, 0xcd, 0x12,
+		0xd5, 0xba, 0x81, 0xc6, 0xe3, 0x8c, 0x5c, 0x22, 0xba, 0x94, 0xf8, 0x4e,
+		0x64, 0x62, 0xf9, 0x24, 0x20, 0xab, 0x7d, 0x5c, 0xe6, 0x5e, 0x3a, 0x7d,
+		0xb2, 0x48, 0xd5, 0x79, 0xd6, 0xe3, 0xcc, 0xa9, 0xfb, 0x64, 0xa4, 0xb0,
+		0xbe, 0xa0, 0x7d, 0x2a, 0x9f, 0x9d, 0xaa, 0x77, 0x83, 0x46, 0x42, 0xa8,
+		0xba, 0xa2, 0x66, 0x46, 0xa0, 0x34, 0x64, 0x6c, 0x87, 0x46, 0xb5, 0x94,
+		0xa3, 0x7e, 0x63, 0x78, 0x22, 0x4a, 0x4d, 0x5b, 0x0e, 0x4b, 0x80, 0x52,
+		0x9a, 0x86, 0xa4, 0x98, 0xe2, 0x79, 0x68, 0x1e, 0x79, 0x52, 0x9c, 0x14,
+		0x39, 0x90, 0x46, 0xaf, 0x45, 0xaa, 0x0e, 0x9d, 0x8a, 0x6c, 0xcf, 0xc4,
+		0x7d, 0xcb, 0xaa, 0x66, 0xfd, 0x6a, 0xf5, 0xdf, 0xd9, 0xe4, 0x3f, 0xe9,
+		0xef, 0xbb, 0x06, 0x37, 0xb5, 0xe2, 0x7e, 0x01, 0x37, 0x90, 0x9a, 0x03,
+		0x7e, 0xb3, 0x9c, 0xd3, 0x0f, 0x07, 0x1d, 0x16, 0xfd, 0xfd, 0x1c, 0x16,
+		0x46, 0x7f, 0xbb, 0x28, 0x64, 0x41, 0x3f, 0xfa, 0x85, 0xa0, 0xa9, 0xfa,
+		0x69, 0x6a, 0x2c, 0x39, 0x58, 0x23, 0x8c, 0x50, 0x94, 0xde, 0xcd, 0xa7,
+		0x55, 0xa7, 0xf8, 0xda, 0x36, 0x46, 0xf5, 0x72, 0xde, 0x24, 0x37, 0xb4,
+		0xab, 0xf5, 0xeb, 0x0d, 0xd9, 0x86, 0xc2, 0x58, 0xe3, 0xe9, 0x71, 0x5a,
+		0x6c, 0x9c, 0xa5, 0xa7, 0xa7, 0x3a, 0x3f, 0x60, 0xbb, 0x7c, 0xf7, 0xc0,
+		0x0f, 0x03, 0x2d, 0x7a, 0x2b, 0x32, 0x81, 0xad, 0xe8, 0x29, 0x40, 0xa6,
+		0xf0, 0x40, 0x4c, 0xa7, 0x58, 0x21, 0x35, 0x7e, 0xbf, 0x54, 0xa8, 0xf7,
+		0x91, 0x31, 0xe8, 0x4c, 0xd2, 0x9c, 0x0c, 0xc8, 0xd6, 0x49, 0xfb, 0xd5,
+		0xd7, 0x09, 0x60, 0x34, 0x3d, 0x0d, 0xdc, 0xa9, 0xdf, 0xf3, 0x1b, 0x91,
+		0x3f, 0x0e, 0x1f, 0x10, 0xaf, 0x19, 0xd2, 0xd5, 0xd2, 0x3c, 0x72, 0x80,
+		0x95, 0x02, 0x17, 0x38, 0x98, 0xe4, 0xdc, 0xe2, 0x61, 0xcc, 0x0d, 0x83,
+		0x8f, 0x7b, 0xbf, 0x04, 0xf4, 0x46, 0xe8, 0xd5, 0x2e, 0x30, 0x4e, 0x20,
+		0x46, 0x0a, 0x99, 0x1a, 0x81, 0x26, 0x66, 0xfa, 0x39, 0x3e, 0x87, 0xef,
+		0x50, 0x4e, 0xc2, 0x0c, 0x0c, 0xd9, 0x4f, 0x3f, 0xe1, 0x6b, 0x8f, 0x21,
+		0x9e, 0xc7, 0x27, 0x86, 0xaf, 0xe7, 0xfc, 0x0e, 0x19, 0xea, 0xcc, 0x19,
+		0x9a, 0x07, 0xcb, 0x77, 0x26, 0x60, 0xe7, 0x29, 0xf5, 0x7b, 0xa4, 0x75,
+		0x8c, 0xce, 0x7a, 0xfd, 0xb5, 0x5e, 0x03, 0x8c, 0xcf, 0x57, 0x5f, 0x69,
+		0x66, 0x3f, 0xe4, 0x02, 0x15, 0xc5, 0x3c, 0xdb, 0x90, 0x38, 0xc4, 0x80,
+		0x64, 0x79, 0x19, 0xf3, 0x84, 0xd3, 0xc3, 0xc1, 0x8c, 0xde, 0x13, 0x52,
+		0x88, 0x4d, 0x27, 0xb6, 0xcd, 0xf5, 0xd6, 0x08, 0xb3, 0x79, 0xdc, 0x69,
+		0x20, 0x20, 0xdb, 0xe5, 0xfe, 0xac, 0xe1, 0x4f, 0x7a, 0xc2, 0xa9, 0xaa,
+		0x7e, 0x59, 0xf2, 0xe9, 0xed, 0xa3, 0x7c, 0xe9, 0xae, 0x0c, 0x92, 0x22,
+		0x05, 0x85, 0x73, 0xbe, 0x23, 0xb9, 0xbb, 0x36, 0x8a, 0xcb, 0x04, 0x03,
+		0x69, 0x80, 0x94, 0x33, 0xf6, 0x9a, 0xaf, 0xac, 0x3e, 0xe8, 0x11, 0x87,
+		0xc5, 0x86, 0xf7, 0x6a, 0xc1, 0xb3, 0x66, 0x7b, 0x01, 0x26, 0x09, 0xae,
+		0x7b, 0x8e, 0x6b, 0xb3, 0x5c, 0x54, 0x21, 0xa3, 0xa3, 0x9c, 0xc2, 0x61,
+		0x99, 0x89, 0x87, 0x32, 0x2f, 0x2c, 0xe3, 0xfc, 0x49, 0x4c, 0xd0, 0x91,
+		0xe6, 0xdb, 0x35, 0xbe, 0xc1, 0xe0, 0xac, 0xd9, 0xcd, 0xd4, 0xaf, 0xcf,
+		0x9b, 0x57, 0x3d, 0x93, 0x66, 0x9e, 0xd8, 0x89, 0x16, 0xb7, 0xec, 0xf4,
+		0xdc, 0x64, 0xbe, 0x2d, 0xc2, 0x40, 0x84, 0xf3, 0x0c, 0x17, 0x8a, 0xa8,
+		0x36, 0x5e, 0x39, 0x54, 0x9b, 0x34, 0xd9, 0x6a, 0x4c, 0xf1, 0xcf, 0x1e,
+		0x7c, 0x32, 0xd6, 0x6f, 0x4f, 0xe9, 0xdf, 0x3e, 0x89, 0x97, 0x1e, 0xe6,
+		0xd4, 0x51, 0x20, 0xff, 0x45, 0x61, 0xc6, 0x6e, 0x48, 0x4d, 0x4e, 0xe6,
+		0x43, 0x93, 0xc4, 0xab, 0x9c, 0xf8, 0xa5, 0x75, 0xf0, 0xb1, 0xf1, 0x10,
+		0xdd, 0xda, 0x6b, 0x1d, 0x6e, 0x7d, 0x5f, 0x93, 0x5f, 0x6c, 0x7a, 0x2a,
+		0xf1, 0x9a, 0xa6, 0xdb, 0xe4, 0xe5, 0xa7, 0xe6, 0x7b, 0x7c, 0x14, 0x6e,
+		0x98, 0x70, 0xfb, 0x75, 0x98, 0x6d, 0x3d, 0xd4, 0x5a, 0x03, 0x97, 0xb8,
+		0x97, 0x07, 0x4f, 0x40, 0x19, 0xd3, 0x7f, 0x73, 0x79, 0x90, 0x45, 0x5a,
+		0x4a, 0x78, 0x6c, 0x1e, 0x7a, 0x8f, 0xe9, 0x9f, 0x8f, 0x9b, 0xff, 0x02,
+		0x61, 0xd2, 0xcf, 0xff, 0x21, 0x3b, 0x17, 0xe8, 0x24, 0x3e, 0x2c, 0xf5,
+		0xec, 0x9d, 0x45, 0xe0, 0x21, 0x26, 0x7f, 0x0f, 0x00, 0x00, 0xff, 0xff,
+		0xc9, 0x2e, 0x07, 0x65, 0xc7, 0x19, 0x00, 0x00,
+	},
 		"conf/app.ini",
 	)
 }
@@ -893,7 +909,7 @@ func conf_content_git_bare_zip() ([]byte, error) {
 		0x28, 0x3f, 0xfe, 0xba, 0x0a, 0x40, 0x72, 0xf5, 0xcf, 0xf9, 0x6a, 0x9b,
 		0x11, 0xa6, 0xf9, 0x31, 0xfa, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x01,
 		0x81, 0x55, 0x99, 0xb6, 0x26, 0x00, 0x00,
-		},
+	},
 		"conf/content/git-bare.zip",
 	)
 }
@@ -946,7 +962,7 @@ func conf_etc_supervisord_conf() ([]byte, error) {
 		0x8d, 0x88, 0x90, 0x28, 0xbf, 0x3f, 0xd4, 0xfe, 0xe7, 0x05, 0xbd, 0x28,
 		0xc2, 0x24, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x75, 0xb0, 0x31,
 		0xf7, 0x04, 0x00, 0x00,
-		},
+	},
 		"conf/etc/supervisord.conf",
 	)
 }
@@ -970,7 +986,7 @@ func conf_gitignore_android() ([]byte, error) {
 		0xb8, 0xa3, 0xb5, 0xe2, 0x2c, 0x81, 0x0c, 0xe2, 0x75, 0xc9, 0xf2, 0x07,
 		0x2f, 0x5e, 0x58, 0x0b, 0x39, 0x3d, 0xa4, 0xf9, 0x3f, 0x00, 0x00, 0xff,
 		0xff, 0x00, 0x96, 0x67, 0x2c, 0x0e, 0x01, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Android",
 	)
 }
@@ -989,7 +1005,7 @@ func conf_gitignore_c() ([]byte, error) {
 		0xeb, 0x8e, 0x79, 0xeb, 0x31, 0x1d, 0x73, 0xb8, 0xa3, 0x8d, 0x6e, 0xdd,
 		0xea, 0xd7, 0xf5, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xca, 0x54, 0xa9, 0x22,
 		0x8f, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/C",
 	)
 }
@@ -1065,7 +1081,7 @@ func conf_gitignore_c_sharp() ([]byte, error) {
 		0x8b, 0x52, 0xd1, 0xf6, 0x63, 0x0e, 0x6e, 0xd8, 0x98, 0xaa, 0x6a, 0xd8,
 		0xb4, 0xfb, 0xc9, 0x76, 0x55, 0xfd, 0xd7, 0x1f, 0x9f, 0xfe, 0x0b, 0x00,
 		0x00, 0xff, 0xff, 0xfe, 0xac, 0xdb, 0x69, 0xf1, 0x05, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/C Sharp",
 	)
 }
@@ -1081,7 +1097,7 @@ func conf_gitignore_c_() ([]byte, error) {
 		0x5c, 0x92, 0x58, 0x82, 0xa6, 0x32, 0x27, 0x31, 0x13, 0x4c, 0x02, 0x89,
 		0x44, 0x2e, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xe6, 0x21, 0x26,
 		0x7e, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/C++",
 	)
 }
@@ -1106,7 +1122,7 @@ func conf_gitignore_google_go() ([]byte, error) {
 		0x22, 0xd5, 0x42, 0x03, 0xe7, 0x8f, 0xcc, 0x91, 0xf3, 0x76, 0xe7, 0x08,
 		0x5a, 0xe9, 0x27, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xab, 0x59, 0x6f, 0xfb,
 		0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Google Go",
 	)
 }
@@ -1128,7 +1144,7 @@ func conf_gitignore_java() ([]byte, error) {
 		0xc4, 0xd9, 0x51, 0x29, 0x52, 0x96, 0x20, 0x55, 0xb3, 0x54, 0x7b, 0x4f,
 		0x6c, 0x82, 0x2e, 0x7d, 0x5c, 0x72, 0x5c, 0xc7, 0x47, 0x00, 0x00, 0x00,
 		0xff, 0xff, 0xe7, 0xd6, 0xf7, 0xa4, 0xbc, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Java",
 	)
 }
@@ -1153,7 +1169,7 @@ func conf_gitignore_objective_c() ([]byte, error) {
 		0xc9, 0x07, 0xae, 0xa1, 0xb9, 0x4c, 0x22, 0x3f, 0x5b, 0x4d, 0x65, 0x7b,
 		0x3d, 0x9f, 0x60, 0x5c, 0x71, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa9,
 		0x17, 0x4f, 0x2a, 0x18, 0x01, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Objective-C",
 	)
 }
@@ -1180,7 +1196,7 @@ func conf_gitignore_python() ([]byte, error) {
 		0x78, 0xeb, 0xf6, 0x9c, 0x58, 0x85, 0x7f, 0x28, 0x58, 0x2b, 0xb6, 0xa6,
 		0x1c, 0xdd, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0xf0, 0xe2, 0xc0,
 		0x3a, 0x01, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Python",
 	)
 }
@@ -1200,7 +1216,7 @@ func conf_gitignore_ruby() ([]byte, error) {
 		0x41, 0xb1, 0xbc, 0x23, 0x4d, 0xdf, 0x7d, 0xf0, 0x88, 0x6c, 0xbf, 0x3b,
 		0xf1, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xca, 0xf7, 0x91, 0x9e,
 		0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Ruby",
 	)
 }
@@ -2189,7 +2205,7 @@ func conf_license_affero_gpl() ([]byte, error) {
 		0x42, 0xc2, 0x5f, 0x88, 0x57, 0x1b, 0xd8, 0x89, 0x3e, 0x15, 0x0e, 0xb8,
 		0x30, 0xdf, 0x60, 0x88, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xd2,
 		0xa8, 0x4c, 0xc3, 0x86, 0x00, 0x00,
-		},
+	},
 		"conf/license/Affero GPL",
 	)
 }
@@ -2527,7 +2543,7 @@ func conf_license_apache_v2_license() ([]byte, error) {
 		0x37, 0x23, 0x02, 0x0e, 0x94, 0x00, 0x65, 0xa1, 0x3f, 0x7d, 0xb3, 0x2f,
 		0x4c, 0x4b, 0x32, 0x5f, 0x77, 0xe7, 0xe7, 0x9a, 0xff, 0x6f, 0x00, 0x00,
 		0x00, 0xff, 0xff, 0xa8, 0x76, 0x8d, 0x12, 0x3b, 0x2c, 0x00, 0x00,
-		},
+	},
 		"conf/license/Apache v2 License",
 	)
 }
@@ -2809,7 +2825,7 @@ func conf_license_artistic_license_2_0() ([]byte, error) {
 		0xdd, 0x89, 0x97, 0xd6, 0xcf, 0xf7, 0x8f, 0x92, 0x0f, 0xb9, 0xfb, 0x77,
 		0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x26, 0x8b, 0xf2, 0xb7, 0x22, 0x00,
 		0x00,
-		},
+	},
 		"conf/license/Artistic License 2.0",
 	)
 }
@@ -2883,7 +2899,7 @@ func conf_license_bsd_3_clause_license() ([]byte, error) {
 		0x95, 0xb8, 0xec, 0x49, 0x8a, 0xac, 0xd8, 0xd0, 0x39, 0xee, 0xdb, 0xbf,
 		0x02, 0x00, 0x00, 0xff, 0xff, 0x84, 0xcd, 0xba, 0x22, 0xc1, 0x05, 0x00,
 		0x00,
-		},
+	},
 		"conf/license/BSD (3-Clause) License",
 	)
 }
@@ -3459,7 +3475,7 @@ func conf_license_gpl_v2() ([]byte, error) {
 		0x4d, 0xee, 0x25, 0x41, 0xb2, 0x70, 0x4f, 0xe6, 0xf0, 0xef, 0xb7, 0x30,
 		0xc7, 0xff, 0x7e, 0x8b, 0x83, 0x22, 0xe6, 0xff, 0x06, 0x00, 0x00, 0xff,
 		0xff, 0x82, 0x4d, 0xf9, 0x2b, 0x69, 0x46, 0x00, 0x00,
-		},
+	},
 		"conf/license/GPL v2",
 	)
 }
@@ -3521,7 +3537,7 @@ func conf_license_mit_license() ([]byte, error) {
 		0x42, 0x26, 0x78, 0x8e, 0x5c, 0x15, 0x81, 0x29, 0xe2, 0xdb, 0xf0, 0xd3,
 		0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0x86, 0xab, 0x31, 0x29, 0x04,
 		0x00, 0x00,
-		},
+	},
 		"conf/license/MIT License",
 	)
 }
@@ -3537,7 +3553,7 @@ func conf_mysql_sql() ([]byte, error) {
 		0xa1, 0xe0, 0xec, 0xef, 0xe3, 0x03, 0xd2, 0x06, 0xe2, 0xc4, 0xa7, 0xa7,
 		0xe6, 0xa5, 0x16, 0x25, 0xe6, 0xc4, 0x27, 0x67, 0x5a, 0x73, 0x01, 0x02,
 		0x00, 0x00, 0xff, 0xff, 0xcd, 0xf5, 0x53, 0x80, 0x6d, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/mysql.sql",
 	)
 }
@@ -3557,12 +3573,11 @@ func conf_supervisor_ini() ([]byte, error) {
 		0xa1, 0xed, 0x82, 0x8e, 0x38, 0x6f, 0x11, 0x92, 0x13, 0x67, 0x75, 0xe7,
 		0xeb, 0xe5, 0xe4, 0x86, 0xef, 0xd7, 0xc1, 0x18, 0xfa, 0x04, 0x00, 0x00,
 		0xff, 0xff, 0x61, 0x60, 0x15, 0x6f, 0xc9, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/supervisor.ini",
 	)
 }
 
-
 // Asset loads and returns the asset for the given name.
 // It returns an error if the asset could not be found or
 // could not be loaded.
@@ -3584,7 +3599,7 @@ func AssetNames() []string {
 }
 
 // _bindata is a table, holding each asset generator, mapped to its name.
-var _bindata = map[string] func() ([]byte, error) {
+var _bindata = map[string]func() ([]byte, error){
 	"conf/app.ini": conf_app_ini,
 	"conf/content/git-bare.zip": conf_content_git_bare_zip,
 	"conf/etc/supervisord.conf": conf_etc_supervisord_conf,

+ 27 - 0
modules/cron/constantdelay.go

@@ -0,0 +1,27 @@
+package cron
+
+import "time"
+
+// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
+// It does not support jobs more frequent than once a second.
+type ConstantDelaySchedule struct {
+	Delay time.Duration
+}
+
+// Every returns a crontab Schedule that activates once every duration.
+// Delays of less than a second are not supported (will round up to 1 second).
+// Any fields less than a Second are truncated.
+func Every(duration time.Duration) ConstantDelaySchedule {
+	if duration < time.Second {
+		duration = time.Second
+	}
+	return ConstantDelaySchedule{
+		Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
+	}
+}
+
+// Next returns the next time this should be run.
+// This rounds so that the next activation time will be on the second.
+func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
+	return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
+}

+ 54 - 0
modules/cron/constantdelay_test.go

@@ -0,0 +1,54 @@
+package cron
+
+import (
+	"testing"
+	"time"
+)
+
+func TestConstantDelayNext(t *testing.T) {
+	tests := []struct {
+		time     string
+		delay    time.Duration
+		expected string
+	}{
+		// Simple cases
+		{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
+		{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
+		{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
+
+		// Wrap around hours
+		{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
+
+		// Wrap around days
+		{"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"},
+		{"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"},
+		{"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"},
+		{"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"},
+
+		// Wrap around months
+		{"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"},
+
+		// Wrap around minute, hour, day, month, and year
+		{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
+
+		// Round to nearest second on the delay
+		{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
+
+		// Round up to 1 second if the duration is less.
+		{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
+
+		// Round to nearest second when calculating the next time.
+		{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
+
+		// Round to nearest second for both.
+		{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
+	}
+
+	for _, c := range tests {
+		actual := Every(c.delay).Next(getTime(c.time))
+		expected := getTime(c.expected)
+		if actual != expected {
+			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual)
+		}
+	}
+}

+ 203 - 7
modules/cron/cron.go

@@ -1,3 +1,4 @@
+// Copyright 2012 Rob Figueiredo. All rights reserved.
 // Copyright 2014 The Gogs Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
@@ -5,13 +6,208 @@
 package cron
 
 import (
-	"github.com/robfig/cron"
-
-	"github.com/gogits/gogs/models"
+	"sort"
+	"time"
 )
 
-func NewCronContext() {
-	c := cron.New()
-	c.AddFunc("@every 1h", models.MirrorUpdate)
-	c.Start()
+// Cron keeps track of any number of entries, invoking the associated func as
+// specified by the schedule. It may be started, stopped, and the entries may
+// be inspected while running.
+type Cron struct {
+	entries  []*Entry
+	stop     chan struct{}
+	add      chan *Entry
+	snapshot chan []*Entry
+	running  bool
+}
+
+// Job is an interface for submitted cron jobs.
+type Job interface {
+	Run()
+}
+
+// The Schedule describes a job's duty cycle.
+type Schedule interface {
+	// Return the next activation time, later than the given time.
+	// Next is invoked initially, and then each time the job is run.
+	Next(time.Time) time.Time
+}
+
+// Entry consists of a schedule and the func to execute on that schedule.
+type Entry struct {
+	Description string
+	Spec        string
+
+	// The schedule on which this job should be run.
+	Schedule Schedule
+
+	// The next time the job will run. This is the zero time if Cron has not been
+	// started or this entry's schedule is unsatisfiable
+	Next time.Time
+
+	// The last time this job was run. This is the zero time if the job has never
+	// been run.
+	Prev time.Time
+
+	// The Job to run.
+	Job Job
+
+	ExecTimes int // Execute times count.
+}
+
+// byTime is a wrapper for sorting the entry array by time
+// (with zero time at the end).
+type byTime []*Entry
+
+func (s byTime) Len() int      { return len(s) }
+func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s byTime) Less(i, j int) bool {
+	// Two zero times should return false.
+	// Otherwise, zero is "greater" than any other time.
+	// (To sort it at the end of the list.)
+	if s[i].Next.IsZero() {
+		return false
+	}
+	if s[j].Next.IsZero() {
+		return true
+	}
+	return s[i].Next.Before(s[j].Next)
+}
+
+// New returns a new Cron job runner.
+func New() *Cron {
+	return &Cron{
+		entries:  nil,
+		add:      make(chan *Entry),
+		stop:     make(chan struct{}),
+		snapshot: make(chan []*Entry),
+		running:  false,
+	}
+}
+
+// A wrapper that turns a func() into a cron.Job
+type FuncJob func()
+
+func (f FuncJob) Run() { f() }
+
+// AddFunc adds a func to the Cron to be run on the given schedule.
+func (c *Cron) AddFunc(desc, spec string, cmd func()) error {
+	return c.AddJob(desc, spec, FuncJob(cmd))
+}
+
+// AddFunc adds a Job to the Cron to be run on the given schedule.
+func (c *Cron) AddJob(desc, spec string, cmd Job) error {
+	schedule, err := Parse(spec)
+	if err != nil {
+		return err
+	}
+	c.Schedule(desc, spec, schedule, cmd)
+	return nil
+}
+
+// Schedule adds a Job to the Cron to be run on the given schedule.
+func (c *Cron) Schedule(desc, spec string, schedule Schedule, cmd Job) {
+	entry := &Entry{
+		Description: desc,
+		Spec:        spec,
+		Schedule:    schedule,
+		Job:         cmd,
+	}
+	if !c.running {
+		c.entries = append(c.entries, entry)
+		return
+	}
+
+	c.add <- entry
+}
+
+// Entries returns a snapshot of the cron entries.
+func (c *Cron) Entries() []*Entry {
+	if c.running {
+		c.snapshot <- nil
+		x := <-c.snapshot
+		return x
+	}
+	return c.entrySnapshot()
+}
+
+// Start the cron scheduler in its own go-routine.
+func (c *Cron) Start() {
+	c.running = true
+	go c.run()
+}
+
+// Run the scheduler.. this is private just due to the need to synchronize
+// access to the 'running' state variable.
+func (c *Cron) run() {
+	// Figure out the next activation times for each entry.
+	now := time.Now().Local()
+	for _, entry := range c.entries {
+		entry.Next = entry.Schedule.Next(now)
+	}
+
+	for {
+		// Determine the next entry to run.
+		sort.Sort(byTime(c.entries))
+
+		var effective time.Time
+		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
+			// If there are no entries yet, just sleep - it still handles new entries
+			// and stop requests.
+			effective = now.AddDate(10, 0, 0)
+		} else {
+			effective = c.entries[0].Next
+		}
+
+		select {
+		case now = <-time.After(effective.Sub(now)):
+			// Run every entry whose next time was this effective time.
+			for _, e := range c.entries {
+				if e.Next != effective {
+					break
+				}
+				go e.Job.Run()
+				e.ExecTimes++
+				e.Prev = e.Next
+				e.Next = e.Schedule.Next(effective)
+			}
+			continue
+
+		case newEntry := <-c.add:
+			c.entries = append(c.entries, newEntry)
+			newEntry.Next = newEntry.Schedule.Next(now)
+
+		case <-c.snapshot:
+			c.snapshot <- c.entrySnapshot()
+
+		case <-c.stop:
+			return
+		}
+
+		// 'now' should be updated after newEntry and snapshot cases.
+		now = time.Now().Local()
+	}
+}
+
+// Stop the cron scheduler.
+func (c *Cron) Stop() {
+	c.stop <- struct{}{}
+	c.running = false
+}
+
+// entrySnapshot returns a copy of the current cron entry list.
+func (c *Cron) entrySnapshot() []*Entry {
+	entries := make([]*Entry, 0, len(c.entries))
+	for _, e := range c.entries {
+		entries = append(entries, &Entry{
+			Description: e.Description,
+			Spec:        e.Spec,
+			Schedule:    e.Schedule,
+			Next:        e.Next,
+			Prev:        e.Prev,
+			Job:         e.Job,
+			ExecTimes:   e.ExecTimes,
+		})
+	}
+	return entries
 }

+ 255 - 0
modules/cron/cron_test.go

@@ -0,0 +1,255 @@
+package cron
+
+import (
+	"fmt"
+	"sync"
+	"testing"
+	"time"
+)
+
+// Many tests schedule a job for every second, and then wait at most a second
+// for it to run.  This amount is just slightly larger than 1 second to
+// compensate for a few milliseconds of runtime.
+const ONE_SECOND = 1*time.Second + 10*time.Millisecond
+
+// Start and stop cron with no entries.
+func TestNoEntries(t *testing.T) {
+	cron := New()
+	cron.Start()
+
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-stop(cron):
+	}
+}
+
+// Start, stop, then add an entry. Verify entry doesn't run.
+func TestStopCausesJobsToNotRun(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	cron := New()
+	cron.Start()
+	cron.Stop()
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+	select {
+	case <-time.After(ONE_SECOND):
+		// No job ran!
+	case <-wait(wg):
+		t.FailNow()
+	}
+}
+
+// Add a job, start cron, expect it runs.
+func TestAddBeforeRunning(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	cron := New()
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+	cron.Start()
+	defer cron.Stop()
+
+	// Give cron 2 seconds to run our job (which is always activated).
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+}
+
+// Start cron, add a job, expect it runs.
+func TestAddWhileRunning(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	cron := New()
+	cron.Start()
+	defer cron.Stop()
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+}
+
+// Test timing with Entries.
+func TestSnapshotEntries(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	cron := New()
+	cron.AddFunc("", "@every 2s", func() { wg.Done() })
+	cron.Start()
+	defer cron.Stop()
+
+	// Cron should fire in 2 seconds. After 1 second, call Entries.
+	select {
+	case <-time.After(ONE_SECOND):
+		cron.Entries()
+	}
+
+	// Even though Entries was called, the cron should fire at the 2 second mark.
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+
+}
+
+// Test that the entries are correctly sorted.
+// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
+// that the immediate entry runs immediately.
+// Also: Test that multiple jobs run in the same instant.
+func TestMultipleEntries(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(2)
+
+	cron := New()
+	cron.AddFunc("", "0 0 0 1 1 ?", func() {})
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+	cron.AddFunc("", "0 0 0 31 12 ?", func() {})
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+	cron.Start()
+	defer cron.Stop()
+
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+}
+
+// Test running the same job twice.
+func TestRunningJobTwice(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(2)
+
+	cron := New()
+	cron.AddFunc("", "0 0 0 1 1 ?", func() {})
+	cron.AddFunc("", "0 0 0 31 12 ?", func() {})
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+	cron.Start()
+	defer cron.Stop()
+
+	select {
+	case <-time.After(2 * ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+}
+
+func TestRunningMultipleSchedules(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(2)
+
+	cron := New()
+	cron.AddFunc("", "0 0 0 1 1 ?", func() {})
+	cron.AddFunc("", "0 0 0 31 12 ?", func() {})
+	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+	cron.Schedule("", "", Every(time.Minute), FuncJob(func() {}))
+	cron.Schedule("", "", Every(time.Second), FuncJob(func() { wg.Done() }))
+	cron.Schedule("", "", Every(time.Hour), FuncJob(func() {}))
+
+	cron.Start()
+	defer cron.Stop()
+
+	select {
+	case <-time.After(2 * ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+}
+
+// Test that the cron is run in the local time zone (as opposed to UTC).
+func TestLocalTimezone(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	now := time.Now().Local()
+	spec := fmt.Sprintf("%d %d %d %d %d ?",
+		now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month())
+
+	cron := New()
+	cron.AddFunc("", spec, func() { wg.Done() })
+	cron.Start()
+	defer cron.Stop()
+
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+}
+
+type testJob struct {
+	wg   *sync.WaitGroup
+	name string
+}
+
+func (t testJob) Run() {
+	t.wg.Done()
+}
+
+// Simple test using Runnables.
+func TestJob(t *testing.T) {
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	cron := New()
+	cron.AddJob("", "0 0 0 30 Feb ?", testJob{wg, "job0"})
+	cron.AddJob("", "0 0 0 1 1 ?", testJob{wg, "job1"})
+	cron.AddJob("", "* * * * * ?", testJob{wg, "job2"})
+	cron.AddJob("", "1 0 0 1 1 ?", testJob{wg, "job3"})
+	cron.Schedule("", "", Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
+	cron.Schedule("", "", Every(5*time.Minute), testJob{wg, "job5"})
+
+	cron.Start()
+	defer cron.Stop()
+
+	select {
+	case <-time.After(ONE_SECOND):
+		t.FailNow()
+	case <-wait(wg):
+	}
+
+	// Ensure the entries are in the right order.
+	expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
+
+	var actuals []string
+	for _, entry := range cron.Entries() {
+		actuals = append(actuals, entry.Job.(testJob).name)
+	}
+
+	for i, expected := range expecteds {
+		if actuals[i] != expected {
+			t.Errorf("Jobs not in the right order.  (expected) %s != %s (actual)", expecteds, actuals)
+			t.FailNow()
+		}
+	}
+}
+
+func wait(wg *sync.WaitGroup) chan bool {
+	ch := make(chan bool)
+	go func() {
+		wg.Wait()
+		ch <- true
+	}()
+	return ch
+}
+
+func stop(cron *Cron) chan bool {
+	ch := make(chan bool)
+	go func() {
+		cron.Stop()
+		ch <- true
+	}()
+	return ch
+}

+ 129 - 0
modules/cron/doc.go

@@ -0,0 +1,129 @@
+/*
+Package cron implements a cron spec parser and job runner.
+
+Usage
+
+Callers may register Funcs to be invoked on a given schedule.  Cron will run
+them in their own goroutines.
+
+	c := cron.New()
+	c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
+	c.AddFunc("@hourly",      func() { fmt.Println("Every hour") })
+	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
+	c.Start()
+	..
+	// Funcs are invoked in their own goroutine, asynchronously.
+	...
+	// Funcs may also be added to a running Cron
+	c.AddFunc("@daily", func() { fmt.Println("Every day") })
+	..
+	// Inspect the cron job entries' next and previous run times.
+	inspect(c.Entries())
+	..
+	c.Stop()  // Stop the scheduler (does not stop any jobs already running).
+
+CRON Expression Format
+
+A cron expression represents a set of times, using 6 space-separated fields.
+
+	Field name   | Mandatory? | Allowed values  | Allowed special characters
+	----------   | ---------- | --------------  | --------------------------
+	Seconds      | Yes        | 0-59            | * / , -
+	Minutes      | Yes        | 0-59            | * / , -
+	Hours        | Yes        | 0-23            | * / , -
+	Day of month | Yes        | 1-31            | * / , - ?
+	Month        | Yes        | 1-12 or JAN-DEC | * / , -
+	Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?
+
+Note: Month and Day-of-week field values are case insensitive.  "SUN", "Sun",
+and "sun" are equally accepted.
+
+Special Characters
+
+Asterisk ( * )
+
+The asterisk indicates that the cron expression will match for all values of the
+field; e.g., using an asterisk in the 5th field (month) would indicate every
+month.
+
+Slash ( / )
+
+Slashes are used to describe increments of ranges. For example 3-59/15 in the
+1st field (minutes) would indicate the 3rd minute of the hour and every 15
+minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
+that is, an increment over the largest possible range of the field.  The form
+"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
+increment until the end of that specific range.  It does not wrap around.
+
+Comma ( , )
+
+Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
+the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
+
+Hyphen ( - )
+
+Hyphens are used to define ranges. For example, 9-17 would indicate every
+hour between 9am and 5pm inclusive.
+
+Question mark ( ? )
+
+Question mark may be used instead of '*' for leaving either day-of-month or
+day-of-week blank.
+
+Predefined schedules
+
+You may use one of several pre-defined schedules in place of a cron expression.
+
+	Entry                  | Description                                | Equivalent To
+	-----                  | -----------                                | -------------
+	@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 0 1 1 *
+	@monthly               | Run once a month, midnight, first of month | 0 0 0 1 * *
+	@weekly                | Run once a week, midnight on Sunday        | 0 0 0 * * 0
+	@daily (or @midnight)  | Run once a day, midnight                   | 0 0 0 * * *
+	@hourly                | Run once an hour, beginning of hour        | 0 0 * * * *
+
+Intervals
+
+You may also schedule a job to execute at fixed intervals.  This is supported by
+formatting the cron spec like this:
+
+    @every <duration>
+
+where "duration" is a string accepted by time.ParseDuration
+(http://golang.org/pkg/time/#ParseDuration).
+
+For example, "@every 1h30m10s" would indicate a schedule that activates every
+1 hour, 30 minutes, 10 seconds.
+
+Note: The interval does not take the job runtime into account.  For example,
+if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
+it will have only 2 minutes of idle time between each run.
+
+Time zones
+
+All interpretation and scheduling is done in the machine's local time zone (as
+provided by the Go time package (http://www.golang.org/pkg/time).
+
+Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
+not be run!
+
+Thread safety
+
+Since the Cron service runs concurrently with the calling code, some amount of
+care must be taken to ensure proper synchronization.
+
+All cron methods are designed to be correctly synchronized as long as the caller
+ensures that invocations have a clear happens-before ordering between them.
+
+Implementation
+
+Cron entries are stored in an array, sorted by their next activation time.  Cron
+sleeps until the next job is due to be run.
+
+Upon waking:
+ - it runs each entry that is active on that second
+ - it calculates the next run times for the jobs that were run
+ - it re-sorts the array of entries by next activation time.
+ - it goes to sleep until the soonest job.
+*/
+package cron

+ 24 - 0
modules/cron/manager.go

@@ -0,0 +1,24 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package cron
+
+import (
+	"fmt"
+
+	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/setting"
+)
+
+var c = New()
+
+func NewCronContext() {
+	c.AddFunc("Update mirrors", "@every 1h", models.MirrorUpdate)
+	c.AddFunc("Deliver hooks", fmt.Sprintf("@every %dm", setting.WebhookTaskInterval), models.DeliverHooks)
+	c.Start()
+}
+
+func ListEntries() []*Entry {
+	return c.Entries()
+}

+ 231 - 0
modules/cron/parser.go

@@ -0,0 +1,231 @@
+package cron
+
+import (
+	"fmt"
+	"log"
+	"math"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Parse returns a new crontab schedule representing the given spec.
+// It returns a descriptive error if the spec is not valid.
+//
+// It accepts
+//   - Full crontab specs, e.g. "* * * * * ?"
+//   - Descriptors, e.g. "@midnight", "@every 1h30m"
+func Parse(spec string) (_ Schedule, err error) {
+	// Convert panics into errors
+	defer func() {
+		if recovered := recover(); recovered != nil {
+			err = fmt.Errorf("%v", recovered)
+		}
+	}()
+
+	if spec[0] == '@' {
+		return parseDescriptor(spec), nil
+	}
+
+	// Split on whitespace.  We require 5 or 6 fields.
+	// (second) (minute) (hour) (day of month) (month) (day of week, optional)
+	fields := strings.Fields(spec)
+	if len(fields) != 5 && len(fields) != 6 {
+		log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec)
+	}
+
+	// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
+	if len(fields) == 5 {
+		fields = append(fields, "*")
+	}
+
+	schedule := &SpecSchedule{
+		Second: getField(fields[0], seconds),
+		Minute: getField(fields[1], minutes),
+		Hour:   getField(fields[2], hours),
+		Dom:    getField(fields[3], dom),
+		Month:  getField(fields[4], months),
+		Dow:    getField(fields[5], dow),
+	}
+
+	return schedule, nil
+}
+
+// getField returns an Int with the bits set representing all of the times that
+// the field represents.  A "field" is a comma-separated list of "ranges".
+func getField(field string, r bounds) uint64 {
+	// list = range {"," range}
+	var bits uint64
+	ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
+	for _, expr := range ranges {
+		bits |= getRange(expr, r)
+	}
+	return bits
+}
+
+// getRange returns the bits indicated by the given expression:
+//   number | number "-" number [ "/" number ]
+func getRange(expr string, r bounds) uint64 {
+
+	var (
+		start, end, step uint
+		rangeAndStep     = strings.Split(expr, "/")
+		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
+		singleDigit      = len(lowAndHigh) == 1
+	)
+
+	var extra_star uint64
+	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
+		start = r.min
+		end = r.max
+		extra_star = starBit
+	} else {
+		start = parseIntOrName(lowAndHigh[0], r.names)
+		switch len(lowAndHigh) {
+		case 1:
+			end = start
+		case 2:
+			end = parseIntOrName(lowAndHigh[1], r.names)
+		default:
+			log.Panicf("Too many hyphens: %s", expr)
+		}
+	}
+
+	switch len(rangeAndStep) {
+	case 1:
+		step = 1
+	case 2:
+		step = mustParseInt(rangeAndStep[1])
+
+		// Special handling: "N/step" means "N-max/step".
+		if singleDigit {
+			end = r.max
+		}
+	default:
+		log.Panicf("Too many slashes: %s", expr)
+	}
+
+	if start < r.min {
+		log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
+	}
+	if end > r.max {
+		log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
+	}
+	if start > end {
+		log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
+	}
+
+	return getBits(start, end, step) | extra_star
+}
+
+// parseIntOrName returns the (possibly-named) integer contained in expr.
+func parseIntOrName(expr string, names map[string]uint) uint {
+	if names != nil {
+		if namedInt, ok := names[strings.ToLower(expr)]; ok {
+			return namedInt
+		}
+	}
+	return mustParseInt(expr)
+}
+
+// mustParseInt parses the given expression as an int or panics.
+func mustParseInt(expr string) uint {
+	num, err := strconv.Atoi(expr)
+	if err != nil {
+		log.Panicf("Failed to parse int from %s: %s", expr, err)
+	}
+	if num < 0 {
+		log.Panicf("Negative number (%d) not allowed: %s", num, expr)
+	}
+
+	return uint(num)
+}
+
+// getBits sets all bits in the range [min, max], modulo the given step size.
+func getBits(min, max, step uint) uint64 {
+	var bits uint64
+
+	// If step is 1, use shifts.
+	if step == 1 {
+		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
+	}
+
+	// Else, use a simple loop.
+	for i := min; i <= max; i += step {
+		bits |= 1 << i
+	}
+	return bits
+}
+
+// all returns all bits within the given bounds.  (plus the star bit)
+func all(r bounds) uint64 {
+	return getBits(r.min, r.max, 1) | starBit
+}
+
+// parseDescriptor returns a pre-defined schedule for the expression, or panics
+// if none matches.
+func parseDescriptor(spec string) Schedule {
+	switch spec {
+	case "@yearly", "@annually":
+		return &SpecSchedule{
+			Second: 1 << seconds.min,
+			Minute: 1 << minutes.min,
+			Hour:   1 << hours.min,
+			Dom:    1 << dom.min,
+			Month:  1 << months.min,
+			Dow:    all(dow),
+		}
+
+	case "@monthly":
+		return &SpecSchedule{
+			Second: 1 << seconds.min,
+			Minute: 1 << minutes.min,
+			Hour:   1 << hours.min,
+			Dom:    1 << dom.min,
+			Month:  all(months),
+			Dow:    all(dow),
+		}
+
+	case "@weekly":
+		return &SpecSchedule{
+			Second: 1 << seconds.min,
+			Minute: 1 << minutes.min,
+			Hour:   1 << hours.min,
+			Dom:    all(dom),
+			Month:  all(months),
+			Dow:    1 << dow.min,
+		}
+
+	case "@daily", "@midnight":
+		return &SpecSchedule{
+			Second: 1 << seconds.min,
+			Minute: 1 << minutes.min,
+			Hour:   1 << hours.min,
+			Dom:    all(dom),
+			Month:  all(months),
+			Dow:    all(dow),
+		}
+
+	case "@hourly":
+		return &SpecSchedule{
+			Second: 1 << seconds.min,
+			Minute: 1 << minutes.min,
+			Hour:   all(hours),
+			Dom:    all(dom),
+			Month:  all(months),
+			Dow:    all(dow),
+		}
+	}
+
+	const every = "@every "
+	if strings.HasPrefix(spec, every) {
+		duration, err := time.ParseDuration(spec[len(every):])
+		if err != nil {
+			log.Panicf("Failed to parse duration %s: %s", spec, err)
+		}
+		return Every(duration)
+	}
+
+	log.Panicf("Unrecognized descriptor: %s", spec)
+	return nil
+}

+ 117 - 0
modules/cron/parser_test.go

@@ -0,0 +1,117 @@
+package cron
+
+import (
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestRange(t *testing.T) {
+	ranges := []struct {
+		expr     string
+		min, max uint
+		expected uint64
+	}{
+		{"5", 0, 7, 1 << 5},
+		{"0", 0, 7, 1 << 0},
+		{"7", 0, 7, 1 << 7},
+
+		{"5-5", 0, 7, 1 << 5},
+		{"5-6", 0, 7, 1<<5 | 1<<6},
+		{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7},
+
+		{"5-6/2", 0, 7, 1 << 5},
+		{"5-7/2", 0, 7, 1<<5 | 1<<7},
+		{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7},
+
+		{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit},
+		{"*/2", 1, 3, 1<<1 | 1<<3 | starBit},
+	}
+
+	for _, c := range ranges {
+		actual := getRange(c.expr, bounds{c.min, c.max, nil})
+		if actual != c.expected {
+			t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
+		}
+	}
+}
+
+func TestField(t *testing.T) {
+	fields := []struct {
+		expr     string
+		min, max uint
+		expected uint64
+	}{
+		{"5", 1, 7, 1 << 5},
+		{"5,6", 1, 7, 1<<5 | 1<<6},
+		{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
+		{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
+	}
+
+	for _, c := range fields {
+		actual := getField(c.expr, bounds{c.min, c.max, nil})
+		if actual != c.expected {
+			t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
+		}
+	}
+}
+
+func TestBits(t *testing.T) {
+	allBits := []struct {
+		r        bounds
+		expected uint64
+	}{
+		{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
+		{hours, 0xffffff},            // 0-23: 24 ones
+		{dom, 0xfffffffe},            // 1-31: 31 ones, 1 zero
+		{months, 0x1ffe},             // 1-12: 12 ones, 1 zero
+		{dow, 0x7f},                  // 0-6: 7 ones
+	}
+
+	for _, c := range allBits {
+		actual := all(c.r) // all() adds the starBit, so compensate for that..
+		if c.expected|starBit != actual {
+			t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
+				c.r.min, c.r.max, 1, c.expected|starBit, actual)
+		}
+	}
+
+	bits := []struct {
+		min, max, step uint
+		expected       uint64
+	}{
+
+		{0, 0, 1, 0x1},
+		{1, 1, 1, 0x2},
+		{1, 5, 2, 0x2a}, // 101010
+		{1, 4, 2, 0xa},  // 1010
+	}
+
+	for _, c := range bits {
+		actual := getBits(c.min, c.max, c.step)
+		if c.expected != actual {
+			t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
+				c.min, c.max, c.step, c.expected, actual)
+		}
+	}
+}
+
+func TestSpecSchedule(t *testing.T) {
+	entries := []struct {
+		expr     string
+		expected Schedule
+	}{
+		{"* 5 * * * *", &SpecSchedule{all(seconds), 1 << 5, all(hours), all(dom), all(months), all(dow)}},
+		{"@every 5m", ConstantDelaySchedule{time.Duration(5) * time.Minute}},
+	}
+
+	for _, c := range entries {
+		actual, err := Parse(c.expr)
+		if err != nil {
+			t.Error(err)
+		}
+		if !reflect.DeepEqual(actual, c.expected) {
+			t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
+		}
+	}
+}

+ 161 - 0
modules/cron/spec.go

@@ -0,0 +1,161 @@
+package cron
+
+import (
+	"time"
+)
+
+// SpecSchedule specifies a duty cycle (to the second granularity), based on a
+// traditional crontab specification. It is computed initially and stored as bit sets.
+type SpecSchedule struct {
+	Second, Minute, Hour, Dom, Month, Dow uint64
+}
+
+// bounds provides a range of acceptable values (plus a map of name to value).
+type bounds struct {
+	min, max uint
+	names    map[string]uint
+}
+
+// The bounds for each field.
+var (
+	seconds = bounds{0, 59, nil}
+	minutes = bounds{0, 59, nil}
+	hours   = bounds{0, 23, nil}
+	dom     = bounds{1, 31, nil}
+	months  = bounds{1, 12, map[string]uint{
+		"jan": 1,
+		"feb": 2,
+		"mar": 3,
+		"apr": 4,
+		"may": 5,
+		"jun": 6,
+		"jul": 7,
+		"aug": 8,
+		"sep": 9,
+		"oct": 10,
+		"nov": 11,
+		"dec": 12,
+	}}
+	dow = bounds{0, 6, map[string]uint{
+		"sun": 0,
+		"mon": 1,
+		"tue": 2,
+		"wed": 3,
+		"thu": 4,
+		"fri": 5,
+		"sat": 6,
+	}}
+)
+
+const (
+	// Set the top bit if a star was included in the expression.
+	starBit = 1 << 63
+)
+
+// Next returns the next time this schedule is activated, greater than the given
+// time.  If no time can be found to satisfy the schedule, return the zero time.
+func (s *SpecSchedule) Next(t time.Time) time.Time {
+	// General approach:
+	// For Month, Day, Hour, Minute, Second:
+	// Check if the time value matches.  If yes, continue to the next field.
+	// If the field doesn't match the schedule, then increment the field until it matches.
+	// While incrementing the field, a wrap-around brings it back to the beginning
+	// of the field list (since it is necessary to re-verify previous field
+	// values)
+
+	// Start at the earliest possible time (the upcoming second).
+	t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
+
+	// This flag indicates whether a field has been incremented.
+	added := false
+
+	// If no time is found within five years, return zero.
+	yearLimit := t.Year() + 5
+
+WRAP:
+	if t.Year() > yearLimit {
+		return time.Time{}
+	}
+
+	// Find the first applicable month.
+	// If it's this month, then do nothing.
+	for 1<<uint(t.Month())&s.Month == 0 {
+		// If we have to add a month, reset the other parts to 0.
+		if !added {
+			added = true
+			// Otherwise, set the date at the beginning (since the current time is irrelevant).
+			t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
+		}
+		t = t.AddDate(0, 1, 0)
+
+		// Wrapped around.
+		if t.Month() == time.January {
+			goto WRAP
+		}
+	}
+
+	// Now get a day in that month.
+	for !dayMatches(s, t) {
+		if !added {
+			added = true
+			t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
+		}
+		t = t.AddDate(0, 0, 1)
+
+		if t.Day() == 1 {
+			goto WRAP
+		}
+	}
+
+	for 1<<uint(t.Hour())&s.Hour == 0 {
+		if !added {
+			added = true
+			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
+		}
+		t = t.Add(1 * time.Hour)
+
+		if t.Hour() == 0 {
+			goto WRAP
+		}
+	}
+
+	for 1<<uint(t.Minute())&s.Minute == 0 {
+		if !added {
+			added = true
+			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location())
+		}
+		t = t.Add(1 * time.Minute)
+
+		if t.Minute() == 0 {
+			goto WRAP
+		}
+	}
+
+	for 1<<uint(t.Second())&s.Second == 0 {
+		if !added {
+			added = true
+			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location())
+		}
+		t = t.Add(1 * time.Second)
+
+		if t.Second() == 0 {
+			goto WRAP
+		}
+	}
+
+	return t
+}
+
+// dayMatches returns true if the schedule's day-of-week and day-of-month
+// restrictions are satisfied by the given time.
+func dayMatches(s *SpecSchedule, t time.Time) bool {
+	var (
+		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
+		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
+	)
+
+	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
+		return domMatch && dowMatch
+	}
+	return domMatch || dowMatch
+}

+ 173 - 0
modules/cron/spec_test.go

@@ -0,0 +1,173 @@
+package cron
+
+import (
+	"testing"
+	"time"
+)
+
+func TestActivation(t *testing.T) {
+	tests := []struct {
+		time, spec string
+		expected   bool
+	}{
+		// Every fifteen minutes.
+		{"Mon Jul 9 15:00 2012", "0 0/15 * * *", true},
+		{"Mon Jul 9 15:45 2012", "0 0/15 * * *", true},
+		{"Mon Jul 9 15:40 2012", "0 0/15 * * *", false},
+
+		// Every fifteen minutes, starting at 5 minutes.
+		{"Mon Jul 9 15:05 2012", "0 5/15 * * *", true},
+		{"Mon Jul 9 15:20 2012", "0 5/15 * * *", true},
+		{"Mon Jul 9 15:50 2012", "0 5/15 * * *", true},
+
+		// Named months
+		{"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true},
+		{"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false},
+
+		// Everything set.
+		{"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true},
+		{"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true},
+		{"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false},
+		{"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false},
+
+		// Predefined schedules
+		{"Mon Jul 9 15:00 2012", "@hourly", true},
+		{"Mon Jul 9 15:04 2012", "@hourly", false},
+		{"Mon Jul 9 15:00 2012", "@daily", false},
+		{"Mon Jul 9 00:00 2012", "@daily", true},
+		{"Mon Jul 9 00:00 2012", "@weekly", false},
+		{"Sun Jul 8 00:00 2012", "@weekly", true},
+		{"Sun Jul 8 01:00 2012", "@weekly", false},
+		{"Sun Jul 8 00:00 2012", "@monthly", false},
+		{"Sun Jul 1 00:00 2012", "@monthly", true},
+
+		// Test interaction of DOW and DOM.
+		// If both are specified, then only one needs to match.
+		{"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true},
+		{"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true},
+		{"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true},
+
+		// However, if one has a star, then both need to match.
+		{"Sun Jul 15 00:00 2012", "0 * * * * Mon", false},
+		{"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false},
+		{"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false},
+		{"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true},
+		{"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true},
+	}
+
+	for _, test := range tests {
+		sched, err := Parse(test.spec)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+		actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
+		expected := getTime(test.time)
+		if test.expected && expected != actual || !test.expected && expected == actual {
+			t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
+				test.spec, test.time, expected, actual)
+		}
+	}
+}
+
+func TestNext(t *testing.T) {
+	runs := []struct {
+		time, spec string
+		expected   string
+	}{
+		// Simple cases
+		{"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
+		{"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
+		{"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
+
+		// Wrap around hours
+		{"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"},
+
+		// Wrap around days
+		{"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"},
+		{"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"},
+		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"},
+		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"},
+		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"},
+
+		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
+		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
+		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
+
+		// Wrap around months
+		{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
+		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"},
+		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
+
+		// Wrap around years
+		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
+		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
+
+		// Wrap around minute, hour, day, month, and year
+		{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
+
+		// Leap year
+		{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
+
+		// Daylight savings time EST -> EDT
+		{"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
+
+		// Daylight savings time EDT -> EST
+		{"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
+		{"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
+
+		// Unsatisfiable
+		{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
+		{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
+	}
+
+	for _, c := range runs {
+		sched, err := Parse(c.spec)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+		actual := sched.Next(getTime(c.time))
+		expected := getTime(c.expected)
+		if !actual.Equal(expected) {
+			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
+		}
+	}
+}
+
+func TestErrors(t *testing.T) {
+	invalidSpecs := []string{
+		"xyz",
+		"60 0 * * *",
+		"0 60 * * *",
+		"0 0 * * XYZ",
+	}
+	for _, spec := range invalidSpecs {
+		_, err := Parse(spec)
+		if err == nil {
+			t.Error("expected an error parsing: ", spec)
+		}
+	}
+}
+
+func getTime(value string) time.Time {
+	if value == "" {
+		return time.Time{}
+	}
+	t, err := time.Parse("Mon Jan 2 15:04 2006", value)
+	if err != nil {
+		t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
+		if err != nil {
+			t, err = time.Parse("2006-01-02T15:04:05-0700", value)
+			if err != nil {
+				panic(err)
+			}
+			// Daylight savings time tests require location
+			if ny, err := time.LoadLocation("America/New_York"); err == nil {
+				t = t.In(ny)
+			}
+		}
+	}
+
+	return t
+}

+ 0 - 95
modules/hooks/hooks.go

@@ -1,95 +0,0 @@
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package hooks
-
-import (
-	"encoding/json"
-	"time"
-
-	"github.com/gogits/gogs/modules/httplib"
-	"github.com/gogits/gogs/modules/log"
-)
-
-// Hook task types.
-const (
-	HTT_WEBHOOK = iota + 1
-	HTT_SERVICE
-)
-
-type PayloadAuthor struct {
-	Name  string `json:"name"`
-	Email string `json:"email"`
-}
-
-type PayloadCommit struct {
-	Id      string         `json:"id"`
-	Message string         `json:"message"`
-	Url     string         `json:"url"`
-	Author  *PayloadAuthor `json:"author"`
-}
-
-type PayloadRepo struct {
-	Id          int64          `json:"id"`
-	Name        string         `json:"name"`
-	Url         string         `json:"url"`
-	Description string         `json:"description"`
-	Website     string         `json:"website"`
-	Watchers    int            `json:"watchers"`
-	Owner       *PayloadAuthor `json:"author"`
-	Private     bool           `json:"private"`
-}
-
-// Payload represents payload information of hook.
-type Payload struct {
-	Secret  string           `json:"secret"`
-	Ref     string           `json:"ref"`
-	Commits []*PayloadCommit `json:"commits"`
-	Repo    *PayloadRepo     `json:"repository"`
-	Pusher  *PayloadAuthor   `json:"pusher"`
-}
-
-// HookTask represents hook task.
-type HookTask struct {
-	Type int
-	Url  string
-	*Payload
-	ContentType int
-	IsSsl       bool
-}
-
-var (
-	taskQueue = make(chan *HookTask, 1000)
-)
-
-// AddHookTask adds new hook task to task queue.
-func AddHookTask(t *HookTask) {
-	taskQueue <- t
-}
-
-func init() {
-	go handleQueue()
-}
-
-func handleQueue() {
-	for {
-		select {
-		case t := <-taskQueue:
-			// Only support JSON now.
-			data, err := json.MarshalIndent(t.Payload, "", "\t")
-			if err != nil {
-				log.Error("hooks.handleQueue(json): %v", err)
-				continue
-			}
-
-			_, err = httplib.Post(t.Url).SetTimeout(5*time.Second, 5*time.Second).
-				Body(data).Response()
-			if err != nil {
-				log.Error("hooks.handleQueue: Fail to deliver hook: %v", err)
-				continue
-			}
-			log.Info("Hook delivered: %s", string(data))
-		}
-	}
-}

+ 13 - 2
modules/log/log.go

@@ -6,13 +6,16 @@
 package log
 
 import (
+	"fmt"
 	"os"
+	"path"
 
 	"github.com/gogits/logs"
 )
 
 var (
-	loggers []*logs.BeeLogger
+	loggers   []*logs.BeeLogger
+	GitLogger *logs.BeeLogger
 )
 
 func init() {
@@ -33,7 +36,15 @@ func NewLogger(bufLen int64, mode, config string) {
 		loggers = append(loggers, logger)
 	}
 	logger.SetLogFuncCallDepth(3)
-	logger.SetLogger(mode, config)
+	if err := logger.SetLogger(mode, config); err != nil {
+		Fatal("Fail to set logger(%s): %v", mode, err)
+	}
+}
+
+func NewGitLogger(logPath string) {
+	os.MkdirAll(path.Dir(logPath), os.ModePerm)
+	GitLogger = logs.NewLogger(0)
+	GitLogger.SetLogger("file", fmt.Sprintf(`{"level":0,"filename":"%s","rotate":false}`, logPath))
 }
 
 func Trace(format string, v ...interface{}) {

+ 44 - 35
modules/mailer/mail.go

@@ -17,6 +17,15 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
+const (
+	AUTH_ACTIVE           base.TplName = "mail/auth/active"
+	AUTH_REGISTER_SUCCESS base.TplName = "mail/auth/register_success"
+	AUTH_RESET_PASSWORD   base.TplName = "mail/auth/reset_passwd"
+
+	NOTIFY_COLLABORATOR base.TplName = "mail/notify/collaborator"
+	NOTIFY_MENTION      base.TplName = "mail/notify/mention"
+)
+
 // Create New mail message use MailFrom and MailUser
 func NewMailMessageFrom(To []string, from, subject, body string) Message {
 	msg := NewHtmlMessage(To, from, subject, body)
@@ -26,10 +35,10 @@ func NewMailMessageFrom(To []string, from, subject, body string) Message {
 
 // Create New mail message use MailFrom and MailUser
 func NewMailMessage(To []string, subject, body string) Message {
-	return NewMailMessageFrom(To, setting.MailService.User, subject, body)
+	return NewMailMessageFrom(To, setting.MailService.From, subject, body)
 }
 
-func GetMailTmplData(user *models.User) map[interface{}]interface{} {
+func GetMailTmplData(u *models.User) map[interface{}]interface{} {
 	data := make(map[interface{}]interface{}, 10)
 	data["AppName"] = setting.AppName
 	data["AppVer"] = setting.AppVer
@@ -37,84 +46,84 @@ func GetMailTmplData(user *models.User) map[interface{}]interface{} {
 	data["AppLogo"] = setting.AppLogo
 	data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
 	data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
-	if user != nil {
-		data["User"] = user
+	if u != nil {
+		data["User"] = u
 	}
 	return data
 }
 
 // create a time limit code for user active
-func CreateUserActiveCode(user *models.User, startInf interface{}) string {
+func CreateUserActiveCode(u *models.User, startInf interface{}) string {
 	minutes := setting.Service.ActiveCodeLives
-	data := base.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
+	data := base.ToStr(u.Id) + u.Email + u.LowerName + u.Passwd + u.Rands
 	code := base.CreateTimeLimitCode(data, minutes, startInf)
 
 	// add tail hex username
-	code += hex.EncodeToString([]byte(user.LowerName))
+	code += hex.EncodeToString([]byte(u.LowerName))
 	return code
 }
 
 // Send user register mail with active code
-func SendRegisterMail(r *middleware.Render, user *models.User) {
-	code := CreateUserActiveCode(user, nil)
+func SendRegisterMail(r *middleware.Render, u *models.User) {
+	code := CreateUserActiveCode(u, nil)
 	subject := "Register success, Welcome"
 
-	data := GetMailTmplData(user)
+	data := GetMailTmplData(u)
 	data["Code"] = code
-	body, err := r.HTMLString("mail/auth/register_success", data)
+	body, err := r.HTMLString(string(AUTH_REGISTER_SUCCESS), data)
 	if err != nil {
 		log.Error("mail.SendRegisterMail(fail to render): %v", err)
 		return
 	}
 
-	msg := NewMailMessage([]string{user.Email}, subject, body)
-	msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
+	msg := NewMailMessage([]string{u.Email}, subject, body)
+	msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)
 
 	SendAsync(&msg)
 }
 
 // Send email verify active email.
-func SendActiveMail(r *middleware.Render, user *models.User) {
-	code := CreateUserActiveCode(user, nil)
+func SendActiveMail(r *middleware.Render, u *models.User) {
+	code := CreateUserActiveCode(u, nil)
 
 	subject := "Verify your e-mail address"
 
-	data := GetMailTmplData(user)
+	data := GetMailTmplData(u)
 	data["Code"] = code
-	body, err := r.HTMLString("mail/auth/active_email", data)
+	body, err := r.HTMLString(string(AUTH_ACTIVE), data)
 	if err != nil {
 		log.Error("mail.SendActiveMail(fail to render): %v", err)
 		return
 	}
 
-	msg := NewMailMessage([]string{user.Email}, subject, body)
-	msg.Info = fmt.Sprintf("UID: %d, send active mail", user.Id)
+	msg := NewMailMessage([]string{u.Email}, subject, body)
+	msg.Info = fmt.Sprintf("UID: %d, send active mail", u.Id)
 
 	SendAsync(&msg)
 }
 
 // Send reset password email.
-func SendResetPasswdMail(r *middleware.Render, user *models.User) {
-	code := CreateUserActiveCode(user, nil)
+func SendResetPasswdMail(r *middleware.Render, u *models.User) {
+	code := CreateUserActiveCode(u, nil)
 
 	subject := "Reset your password"
 
-	data := GetMailTmplData(user)
+	data := GetMailTmplData(u)
 	data["Code"] = code
-	body, err := r.HTMLString("mail/auth/reset_passwd", data)
+	body, err := r.HTMLString(string(AUTH_RESET_PASSWORD), data)
 	if err != nil {
 		log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
 		return
 	}
 
-	msg := NewMailMessage([]string{user.Email}, subject, body)
-	msg.Info = fmt.Sprintf("UID: %d, send reset password email", user.Id)
+	msg := NewMailMessage([]string{u.Email}, subject, body)
+	msg.Info = fmt.Sprintf("UID: %d, send reset password email", u.Id)
 
 	SendAsync(&msg)
 }
 
 // SendIssueNotifyMail sends mail notification of all watchers of repository.
-func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
+func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
 	ws, err := models.GetWatchers(repo.Id)
 	if err != nil {
 		return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error())
@@ -123,7 +132,7 @@ func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issu
 	tos := make([]string, 0, len(ws))
 	for i := range ws {
 		uid := ws[i].UserId
-		if user.Id == uid {
+		if u.Id == uid {
 			continue
 		}
 		u, err := models.GetUserById(uid)
@@ -141,14 +150,14 @@ func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issu
 	content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
 		base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
 		setting.AppUrl, owner.Name, repo.Name, issue.Index)
-	msg := NewMailMessageFrom(tos, user.Email, subject, content)
+	msg := NewMailMessageFrom(tos, u.Email, subject, content)
 	msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
 	SendAsync(&msg)
 	return tos, nil
 }
 
 // SendIssueMentionMail sends mail notification for who are mentioned in issue.
-func SendIssueMentionMail(r *middleware.Render, user, owner *models.User,
+func SendIssueMentionMail(r *middleware.Render, u, owner *models.User,
 	repo *models.Repository, issue *models.Issue, tos []string) error {
 
 	if len(tos) == 0 {
@@ -161,19 +170,19 @@ func SendIssueMentionMail(r *middleware.Render, user, owner *models.User,
 	data["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
 	data["Subject"] = subject
 
-	body, err := r.HTMLString("mail/notify/mention", data)
+	body, err := r.HTMLString(string(NOTIFY_MENTION), data)
 	if err != nil {
 		return fmt.Errorf("mail.SendIssueMentionMail(fail to render): %v", err)
 	}
 
-	msg := NewMailMessageFrom(tos, user.Email, subject, body)
+	msg := NewMailMessageFrom(tos, u.Email, subject, body)
 	msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
 	SendAsync(&msg)
 	return nil
 }
 
 // SendCollaboratorMail sends mail notification to new collaborator.
-func SendCollaboratorMail(r *middleware.Render, user, owner *models.User,
+func SendCollaboratorMail(r *middleware.Render, u, owner *models.User,
 	repo *models.Repository) error {
 
 	subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name)
@@ -182,13 +191,13 @@ func SendCollaboratorMail(r *middleware.Render, user, owner *models.User,
 	data["RepoLink"] = path.Join(owner.Name, repo.Name)
 	data["Subject"] = subject
 
-	body, err := r.HTMLString("mail/notify/collaborator", data)
+	body, err := r.HTMLString(string(NOTIFY_COLLABORATOR), data)
 	if err != nil {
 		return fmt.Errorf("mail.SendCollaboratorMail(fail to render): %v", err)
 	}
 
-	msg := NewMailMessage([]string{user.Email}, subject, body)
-	msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
+	msg := NewMailMessage([]string{u.Email}, subject, body)
+	msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)
 
 	SendAsync(&msg)
 	return nil

+ 5 - 5
modules/middleware/context.go

@@ -104,12 +104,12 @@ func (ctx *Context) HasError() bool {
 }
 
 // HTML calls render.HTML underlying but reduce one argument.
-func (ctx *Context) HTML(status int, name string, htmlOpt ...HTMLOptions) {
-	ctx.Render.HTML(status, name, ctx.Data, htmlOpt...)
+func (ctx *Context) HTML(status int, name base.TplName, htmlOpt ...HTMLOptions) {
+	ctx.Render.HTML(status, string(name), ctx.Data, htmlOpt...)
 }
 
 // RenderWithErr used for page has form validation but need to prompt error to users.
-func (ctx *Context) RenderWithErr(msg, tpl string, form auth.Form) {
+func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form auth.Form) {
 	if form != nil {
 		auth.AssignForm(form, ctx.Data)
 	}
@@ -133,7 +133,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
 	case 500:
 		ctx.Data["Title"] = "Internal Server Error"
 	}
-	ctx.HTML(status, fmt.Sprintf("status/%d", status))
+	ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status)))
 }
 
 func (ctx *Context) Debug(msg string, args ...interface{}) {
@@ -358,7 +358,7 @@ func InitContext() martini.Handler {
 		})
 
 		// Get user from session if logined.
-		user := auth.SignedInUser(ctx.Session)
+		user := auth.SignedInUser(ctx.req.Header, ctx.Session)
 		ctx.User = user
 		ctx.IsSigned = user != nil
 

+ 5 - 8
modules/middleware/repo.go

@@ -21,21 +21,17 @@ import (
 
 func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 	return func(ctx *Context, params martini.Params) {
-		log.Trace(fmt.Sprint(args))
 		// valid brachname
 		var validBranch bool
 		// display bare quick start if it is a bare repo
 		var displayBare bool
 
 		if len(args) >= 1 {
-			// Note: argument has wrong value in Go1.3 martini.
-			// validBranch = args[0]
-			validBranch = true
+			validBranch = args[0]
 		}
 
 		if len(args) >= 2 {
-			// displayBare = args[1]
-			displayBare = true
+			displayBare = args[1]
 		}
 
 		var (
@@ -48,9 +44,10 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 		repoName := params["reponame"]
 		refName := params["branchname"]
 
+		// TODO: need more advanced onwership and access level check.
 		// Collaborators who have write access can be seen as owners.
 		if ctx.IsSigned {
-			ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.AU_WRITABLE)
+			ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
 			if err != nil {
 				ctx.Handle(500, "RepoAssignment(HasAccess)", err)
 				return
@@ -111,7 +108,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 				return
 			}
 
-			hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.AU_READABLE)
+			hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
 			if err != nil {
 				ctx.Handle(500, "RepoAssignment(HasAccess)", err)
 				return

+ 89 - 0
modules/process/manager.go

@@ -0,0 +1,89 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package process
+
+import (
+	"bytes"
+	"fmt"
+	"os/exec"
+	"time"
+
+	"github.com/gogits/gogs/modules/log"
+)
+
+// Process represents a working process inherit from Gogs.
+type Process struct {
+	Pid         int64 // Process ID, not system one.
+	Description string
+	Start       time.Time
+	Cmd         *exec.Cmd
+}
+
+// List of existing processes.
+var (
+	curPid    int64 = 1
+	Processes []*Process
+)
+
+// Add adds a existing process and returns its PID.
+func Add(desc string, cmd *exec.Cmd) int64 {
+	pid := curPid
+	Processes = append(Processes, &Process{
+		Pid:         pid,
+		Description: desc,
+		Start:       time.Now(),
+		Cmd:         cmd,
+	})
+	curPid++
+	return pid
+}
+
+func ExecDir(dir, desc, cmdName string, args ...string) (string, string, error) {
+	bufOut := new(bytes.Buffer)
+	bufErr := new(bytes.Buffer)
+
+	cmd := exec.Command(cmdName, args...)
+	cmd.Dir = dir
+	cmd.Stdout = bufOut
+	cmd.Stderr = bufErr
+
+	pid := Add(desc, cmd)
+	err := cmd.Run()
+	if errKill := Kill(pid); errKill != nil {
+		log.Error("Exec: %v", pid, desc, errKill)
+	}
+	return bufOut.String(), bufErr.String(), err
+}
+
+// Exec starts executing a command and record its process.
+func Exec(desc, cmdName string, args ...string) (string, string, error) {
+	return ExecDir("", desc, cmdName, args...)
+}
+
+// Remove removes a process from list.
+func Remove(pid int64) {
+	for i, proc := range Processes {
+		if proc.Pid == pid {
+			Processes = append(Processes[:i], Processes[i+1:]...)
+			return
+		}
+	}
+}
+
+// Kill kills and removes a process from list.
+func Kill(pid int64) error {
+	for i, proc := range Processes {
+		if proc.Pid == pid {
+			if proc.Cmd.Process != nil && proc.Cmd.ProcessState != nil && !proc.Cmd.ProcessState.Exited() {
+				if err := proc.Cmd.Process.Kill(); err != nil {
+					return fmt.Errorf("fail to kill process(%d/%s): %v", proc.Pid, proc.Description, err)
+				}
+			}
+			Processes = append(Processes[:i], Processes[i+1:]...)
+			return nil
+		}
+	}
+	return nil
+}

+ 47 - 20
modules/setting/setting.go

@@ -47,11 +47,16 @@ var (
 	StaticRootPath     string
 
 	// Security settings.
-	InstallLock        bool
-	SecretKey          string
-	LogInRememberDays  int
-	CookieUserName     string
-	CookieRememberName string
+	InstallLock          bool
+	SecretKey            string
+	LogInRememberDays    int
+	CookieUserName       string
+	CookieRememberName   string
+	ReverseProxyAuthUser string
+
+	// Webhook settings.
+	WebhookTaskInterval   int
+	WebhookDeliverTimeout int
 
 	// Repository settings.
 	RepoRootPath string
@@ -86,8 +91,7 @@ var (
 	RunUser    string
 )
 
-// WorkDir returns absolute path of work directory.
-func WorkDir() (string, error) {
+func ExecPath() (string, error) {
 	file, err := exec.LookPath(os.Args[0])
 	if err != nil {
 		return "", err
@@ -96,7 +100,13 @@ func WorkDir() (string, error) {
 	if err != nil {
 		return "", err
 	}
-	return path.Dir(strings.Replace(p, "\\", "/", -1)), nil
+	return p, nil
+}
+
+// WorkDir returns absolute path of work directory.
+func WorkDir() (string, error) {
+	execPath, err := ExecPath()
+	return path.Dir(strings.Replace(execPath, "\\", "/", -1)), err
 }
 
 // NewConfigContext initializes configuration context.
@@ -154,6 +164,7 @@ func NewConfigContext() {
 	LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
 	CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
 	CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
+	ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
 
 	RunUser = Cfg.MustValue("", "RUN_USER")
 	curUser := os.Getenv("USER")
@@ -171,6 +182,12 @@ func NewConfigContext() {
 		log.Fatal("Fail to get home directory: %v", err)
 	}
 	RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
+	if !filepath.IsAbs(RepoRootPath) {
+		RepoRootPath = filepath.Join(workDir, RepoRootPath)
+	} else {
+		RepoRootPath = filepath.Clean(RepoRootPath)
+	}
+
 	if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
 		log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err)
 	}
@@ -182,14 +199,15 @@ func NewConfigContext() {
 }
 
 var Service struct {
-	RegisterEmailConfirm bool
-	DisableRegistration  bool
-	RequireSignInView    bool
-	EnableCacheAvatar    bool
-	NotifyMail           bool
-	ActiveCodeLives      int
-	ResetPwdCodeLives    int
-	LdapAuth             bool
+	RegisterEmailConfirm   bool
+	DisableRegistration    bool
+	RequireSignInView      bool
+	EnableCacheAvatar      bool
+	EnableNotifyMail       bool
+	EnableReverseProxyAuth bool
+	LdapAuth               bool
+	ActiveCodeLives        int
+	ResetPwdCodeLives      int
 }
 
 func newService() {
@@ -198,6 +216,7 @@ func newService() {
 	Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
 	Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
 	Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
+	Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION")
 }
 
 var logLevels = map[string]string{
@@ -246,20 +265,20 @@ func newLogService() {
 				Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
 				Cfg.MustInt(modeSec, "MAX_DAYS", 7))
 		case "conn":
-			LogConfigs[i] = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
+			LogConfigs[i] = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
 				Cfg.MustBool(modeSec, "RECONNECT_ON_MSG"),
 				Cfg.MustBool(modeSec, "RECONNECT"),
 				Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
 				Cfg.MustValue(modeSec, "ADDR", ":7020"))
 		case "smtp":
-			LogConfigs[i] = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
+			LogConfigs[i] = fmt.Sprintf(`{"level":%s,"username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
 				Cfg.MustValue(modeSec, "USER", "example@example.com"),
 				Cfg.MustValue(modeSec, "PASSWD", "******"),
 				Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
 				Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
 				Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
 		case "database":
-			LogConfigs[i] = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
+			LogConfigs[i] = fmt.Sprintf(`{"level":%s,"driver":"%s","conn":"%s"}`, level,
 				Cfg.MustValue(modeSec, "DRIVER"),
 				Cfg.MustValue(modeSec, "CONN"))
 		}
@@ -330,6 +349,7 @@ func newSessionService() {
 type Mailer struct {
 	Name         string
 	Host         string
+	From         string
 	User, Passwd string
 }
 
@@ -363,6 +383,7 @@ func newMailService() {
 		User:   Cfg.MustValue("mailer", "USER"),
 		Passwd: Cfg.MustValue("mailer", "PASSWD"),
 	}
+	MailService.From = Cfg.MustValue("mailer", "FROM", MailService.User)
 	log.Info("Mail Service Enabled")
 }
 
@@ -384,10 +405,15 @@ func newNotifyMailService() {
 		log.Warn("Notify Mail Service: Mail Service is not enabled")
 		return
 	}
-	Service.NotifyMail = true
+	Service.EnableNotifyMail = true
 	log.Info("Notify Mail Service Enabled")
 }
 
+func newWebhookService() {
+	WebhookTaskInterval = Cfg.MustInt("webhook", "TASK_INTERVAL", 1)
+	WebhookDeliverTimeout = Cfg.MustInt("webhook", "DELIVER_TIMEOUT", 5)
+}
+
 func NewServices() {
 	newService()
 	newLogService()
@@ -396,4 +422,5 @@ func NewServices() {
 	newMailService()
 	newRegisterMailService()
 	newNotifyMailService()
+	newWebhookService()
 }

+ 5 - 5
modules/social/social.go

@@ -120,7 +120,7 @@ type SocialGithub struct {
 }
 
 func (s *SocialGithub) Type() int {
-	return models.OT_GITHUB
+	return int(models.GITHUB)
 }
 
 func newGitHubOauth(config *oauth.Config) {
@@ -174,7 +174,7 @@ type SocialGoogle struct {
 }
 
 func (s *SocialGoogle) Type() int {
-	return models.OT_GOOGLE
+	return int(models.GOOGLE)
 }
 
 func newGoogleOauth(config *oauth.Config) {
@@ -229,7 +229,7 @@ type SocialTencent struct {
 }
 
 func (s *SocialTencent) Type() int {
-	return models.OT_QQ
+	return int(models.QQ)
 }
 
 func newTencentOauth(config *oauth.Config) {
@@ -295,7 +295,7 @@ type SocialTwitter struct {
 }
 
 func (s *SocialTwitter) Type() int {
-	return models.OT_TWITTER
+	return int(models.TWITTER)
 }
 
 func newTwitterOauth(config *oauth.Config) {
@@ -351,7 +351,7 @@ type SocialWeibo struct {
 }
 
 func (s *SocialWeibo) Type() int {
-	return models.OT_WEIBO
+	return int(models.WEIBO)
 }
 
 func newWeiboOauth(config *oauth.Config) {

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


+ 247 - 3
public/css/gogs.css

@@ -10,7 +10,7 @@ body {
 
 html, body {
     height: 100%;
-    font-family: Helvetica, Arial, sans-serif;
+    font-family: Arial, Helvetica, sans-serif;
 }
 
 /* override bs3 */
@@ -257,6 +257,9 @@ html, body {
 
 .card .btn {
     cursor: pointer;
+}
+
+.card .btn-primary {
     margin-right: 1.2em;
 }
 
@@ -372,7 +375,7 @@ html, body {
 
 /* gogits repo create */
 
-#repo-create {
+#repo-create, #org-create, #org-teams-create, #org-teams-edit {
     width: 800px;
 }
 
@@ -638,6 +641,55 @@ html, body {
     margin: 0 .5em;
 }
 
+#dashboard-switch .btn, #repo-owner-switch .btn {
+    height: 40px;
+}
+
+#dashboard-switch {
+    margin-top: 14px;
+    margin-right: 18px;
+}
+
+#dashboard-switch .dropdown-menu,#repo-owner-switch .dropdown-menu {
+    padding: 0;
+}
+
+#dashboard-switch-menu {
+    width: 180px;
+    margin-bottom: 0;
+    padding-bottom: 0;
+}
+
+#dashboard-switch-menu > li > a {
+    display: block;
+    padding: .8em 1.2em;
+}
+
+#dashboard-switch-menu > li > a:hover {
+    text-decoration: none;
+}
+
+#dashboard-switch-menu > li > a img, #dashboard-switch button img {
+    margin-right: 6px;
+}
+
+#dashboard-switch-menu > li {
+    border-bottom: 1px solid #eaeaea;
+}
+
+#dashboard-switch-menu > li .fa {
+    opacity: 0;
+    margin-right: 16px;
+}
+
+#dashboard-switch-menu > li.checked .fa {
+    opacity: 1;
+}
+
+#dashboard-switch-menu > li:last-child {
+    border-bottom: none;
+}
+
 /* gogits repo single page */
 
 #body-nav.repo-nav {
@@ -1643,7 +1695,7 @@ html, body {
     vertical-align: top;
 }
 
-#label-color-change-ipt2{
+#label-color-change-ipt2 {
     margin-top: 1px;
 }
 
@@ -1814,4 +1866,196 @@ html, body {
 
 #release-preview {
     margin: 6px 0;
+}
+
+/*  organization */
+
+#body-nav.org-nav {
+    height: 140px;
+    padding: 16px 0;
+}
+
+#body-nav.org-nav.org-nav-auto {
+    height: auto;
+}
+
+.org-nav > .container {
+    padding-left: 0;
+    padding-left: 0;
+}
+
+.org-nav .org-logo {
+    margin-right: 16px;
+    width: 100px;
+    height: 100px;
+}
+
+.org-nav .org-small-logo {
+    margin-right: 16px;
+    width: 50px;
+    height: 50px;
+}
+
+.org-nav .org-name {
+    margin-top: 0;
+}
+
+.org-nav-auto .org-name {
+    font-size: 1.4em;
+    line-height: 48px;
+}
+
+#body-nav.org-nav-auto .nav {
+    margin-top: 6px;
+}
+
+#body-nav.org-nav-auto .nav a:hover {
+    text-decoration: none;
+}
+
+.org-description {
+    font-size: 16px;
+}
+
+.org-meta li, .org-meta li a, .org-repo-update, .org-repo-status, .org-team-meta {
+    color: #888;
+}
+
+.org-meta li {
+    margin-right: 12px;
+}
+
+.org-meta li a:hover {
+    text-decoration: underline;
+}
+
+.org-meta .fa {
+    margin-left: 0;
+}
+
+.org-main {
+    padding-left: 0;
+}
+
+.org-sidebar {
+    margin-top: -100px;
+}
+
+.org-panel .panel-heading {
+    font-size: 18px;
+}
+
+.org-repo-status {
+    font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+.org-repo-item {
+    border-bottom: 1px solid #DDD;
+    padding-bottom: 18px;
+}
+
+.org-member img {
+    width: 60px;
+    height: 60px;
+    border-radius: 4px;
+}
+
+.org-member {
+    display: inline-block;
+    padding: 2px;
+}
+
+.org-team-name {
+    font-size: 15px;
+    margin-bottom: 0;
+    color: #444;
+}
+
+.org-team {
+    border-bottom: 1px solid #DDD;
+    margin-bottom: 12px;
+}
+
+.org-team:last-child {
+    border: none;
+}
+
+.org-team a {
+    display: block;
+}
+
+.org-team a:hover {
+    text-decoration: none;
+}
+
+.org-team a:hover .org-team-name {
+    color: #0079bc !important;
+}
+
+#org-members {
+    margin-right: 30px;
+}
+
+#org-members .member .avatar img {
+    width: 50px;
+    height: 50px;
+}
+
+#org-members .member {
+    padding-bottom: 20px;
+    margin-bottom: 20px;
+    border-bottom: 1px solid #DDD;
+    height: 70px;
+}
+
+#org-members .member .name {
+    padding-top: 4px;
+}
+
+#org-members .member .nick {
+    display: block;
+    color: #888;
+}
+
+#org-members .member .name a {
+    color: #444;
+}
+
+#org-members .member .name strong {
+    font-size: 1.2em;
+}
+
+#org-members .status, #org-members .role {
+    line-height: 48px;
+    text-align: right;
+}
+
+#org-teams .org-team .panel-heading {
+    margin-top: 0;
+}
+
+#org-teams .org-team .panel-heading a {
+    color: #444;
+}
+
+#org-teams .org-team-members {
+    margin-top: 18px;
+}
+
+#org-teams .org-team-members img {
+    width: 40px;
+    height: 40px;
+    margin-right: 12px;
+}
+
+#org-teams .org-team-members a {
+    display: inline-block;
+}
+
+#org-teams .org-team .panel-footer {
+    height: 60px;
+}
+
+#org-teams .org-team {
+    border-bottom: none;
 }

BIN
public/fonts/FontAwesome.otf


BIN
public/fonts/fontawesome-webfont.eot


File diff suppressed because it is too large
+ 3 - 165
public/fonts/fontawesome-webfont.svg


BIN
public/fonts/fontawesome-webfont.ttf


BIN
public/fonts/fontawesome-webfont.woff


+ 26 - 2
public/js/app.js

@@ -705,8 +705,8 @@ function initRelease() {
     (function () {
         $('[data-ajax-name=release-preview]').on("click", function () {
             var $this = $(this);
-            $this.toggleAjax(function (json) {
-                $($this.data("preview")).html(json.ok ? json.content : "no content");
+            $this.toggleAjax(function (resp) {
+                $($this.data("preview")).html(resp);
             }, function () {
                 $($this.data("preview")).html("no content");
             })
@@ -758,6 +758,27 @@ function initRepoSetting() {
     });
 }
 
+function initRepoCreating() {
+    // owner switch menu click
+    (function () {
+        $('#repo-owner-switch .dropdown-menu').on("click", "li", function () {
+            var uid = $(this).data('uid');
+            // set to input
+            $('#repo-owner-id').val(uid);
+            // set checked class
+            if (!$(this).hasClass("checked")) {
+                $(this).parent().find(".checked").removeClass("checked");
+                $(this).addClass("checked");
+            }
+            // set button group to show clicked owner
+            $('#repo-owner-avatar').attr("src",$(this).find('img').attr("src"));
+            $('#repo-owner-name').text($(this).text().trim());
+            console.log("set repo owner to uid :",uid,$(this).text().trim());
+        });
+    }());
+    console.log("init repo-creating scripts");
+}
+
 (function ($) {
     $(function () {
         initCore();
@@ -780,6 +801,9 @@ function initRepoSetting() {
         if ($('#repo-setting-container').length) {
             initRepoSetting();
         }
+        if ($('#repo-create').length) {
+            initRepoCreating();
+        }
     });
 })(jQuery);
 

+ 48 - 9
routers/admin/admin.go

@@ -14,10 +14,22 @@ import (
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/cron"
 	"github.com/gogits/gogs/modules/middleware"
+	"github.com/gogits/gogs/modules/process"
 	"github.com/gogits/gogs/modules/setting"
 )
 
+const (
+	DASHBOARD       base.TplName = "admin/dashboard"
+	USERS           base.TplName = "admin/users"
+	REPOS           base.TplName = "admin/repos"
+	AUTHS           base.TplName = "admin/auths"
+	CONFIG          base.TplName = "admin/config"
+	MONITOR_PROCESS base.TplName = "admin/monitor/process"
+	MONITOR_CRON    base.TplName = "admin/monitor/cron"
+)
+
 var startTime = time.Now()
 
 var sysStatus struct {
@@ -100,8 +112,11 @@ func updateSystemStatus() {
 }
 
 // Operation types.
+type AdminOperation int
+
 const (
-	OT_CLEAN_OAUTH = iota + 1
+	CLEAN_UNBIND_OAUTH AdminOperation = iota + 1
+	CLEAN_INACTIVATE_USER
 )
 
 func Dashboard(ctx *middleware.Context) {
@@ -114,10 +129,13 @@ func Dashboard(ctx *middleware.Context) {
 		var err error
 		var success string
 
-		switch op {
-		case OT_CLEAN_OAUTH:
+		switch AdminOperation(op) {
+		case CLEAN_UNBIND_OAUTH:
 			success = "All unbind OAuthes have been deleted."
 			err = models.CleanUnbindOauth()
+		case CLEAN_INACTIVATE_USER:
+			success = "All inactivate accounts have been deleted."
+			err = models.DeleteInactivateUsers()
 		}
 
 		if err != nil {
@@ -132,7 +150,7 @@ func Dashboard(ctx *middleware.Context) {
 	ctx.Data["Stats"] = models.GetStatistic()
 	updateSystemStatus()
 	ctx.Data["SysStatus"] = sysStatus
-	ctx.HTML(200, "admin/dashboard")
+	ctx.HTML(200, DASHBOARD)
 }
 
 func Users(ctx *middleware.Context) {
@@ -142,10 +160,10 @@ func Users(ctx *middleware.Context) {
 	var err error
 	ctx.Data["Users"], err = models.GetUsers(200, 0)
 	if err != nil {
-		ctx.Handle(500, "admin.Users", err)
+		ctx.Handle(500, "admin.Users(GetUsers)", err)
 		return
 	}
-	ctx.HTML(200, "admin/users")
+	ctx.HTML(200, USERS)
 }
 
 func Repositories(ctx *middleware.Context) {
@@ -158,7 +176,7 @@ func Repositories(ctx *middleware.Context) {
 		ctx.Handle(500, "admin.Repositories", err)
 		return
 	}
-	ctx.HTML(200, "admin/repos")
+	ctx.HTML(200, REPOS)
 }
 
 func Auths(ctx *middleware.Context) {
@@ -171,7 +189,7 @@ func Auths(ctx *middleware.Context) {
 		ctx.Handle(500, "admin.Auths", err)
 		return
 	}
-	ctx.HTML(200, "admin/auths")
+	ctx.HTML(200, AUTHS)
 }
 
 func Config(ctx *middleware.Context) {
@@ -188,11 +206,15 @@ func Config(ctx *middleware.Context) {
 	ctx.Data["StaticRootPath"] = setting.StaticRootPath
 	ctx.Data["LogRootPath"] = setting.LogRootPath
 	ctx.Data["ScriptType"] = setting.ScriptType
+	ctx.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser
 
 	ctx.Data["Service"] = setting.Service
 
 	ctx.Data["DbCfg"] = models.DbCfg
 
+	ctx.Data["WebhookTaskInterval"] = setting.WebhookTaskInterval
+	ctx.Data["WebhookDeliverTimeout"] = setting.WebhookDeliverTimeout
+
 	ctx.Data["MailerEnabled"] = false
 	if setting.MailService != nil {
 		ctx.Data["MailerEnabled"] = true
@@ -223,5 +245,22 @@ func Config(ctx *middleware.Context) {
 	}
 	ctx.Data["Loggers"] = loggers
 
-	ctx.HTML(200, "admin/config")
+	ctx.HTML(200, CONFIG)
+}
+
+func Monitor(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Monitoring Center"
+	ctx.Data["PageIsMonitor"] = true
+
+	tab := ctx.Query("tab")
+	switch tab {
+	case "process":
+		ctx.Data["PageIsMonitorProcess"] = true
+		ctx.Data["Processes"] = process.Processes
+		ctx.HTML(200, MONITOR_PROCESS)
+	default:
+		ctx.Data["PageIsMonitorCron"] = true
+		ctx.Data["Entries"] = cron.ListEntries()
+		ctx.HTML(200, MONITOR_CRON)
+	}
 }

+ 23 - 18
routers/admin/auths.go → routers/admin/auth.go

@@ -18,12 +18,17 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	AUTH_NEW  base.TplName = "admin/auth/new"
+	AUTH_EDIT base.TplName = "admin/auth/edit"
+)
+
 func NewAuthSource(ctx *middleware.Context) {
 	ctx.Data["Title"] = "New Authentication"
 	ctx.Data["PageIsAuths"] = true
 	ctx.Data["LoginTypes"] = models.LoginTypes
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
-	ctx.HTML(200, "admin/auths/new")
+	ctx.HTML(200, AUTH_NEW)
 }
 
 func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
@@ -33,13 +38,13 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
 
 	if ctx.HasError() {
-		ctx.HTML(200, "admin/auths/new")
+		ctx.HTML(200, AUTH_NEW)
 		return
 	}
 
 	var u core.Conversion
-	switch form.Type {
-	case models.LT_LDAP:
+	switch models.LoginType(form.Type) {
+	case models.LDAP:
 		u = &models.LDAPConfig{
 			Ldapsource: ldap.Ldapsource{
 				Host:         form.Host,
@@ -53,7 +58,7 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 				Name:         form.AuthName,
 			},
 		}
-	case models.LT_SMTP:
+	case models.SMTP:
 		u = &models.SMTPConfig{
 			Auth: form.SmtpAuth,
 			Host: form.SmtpHost,
@@ -66,15 +71,15 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 	}
 
 	var source = &models.LoginSource{
-		Type:              form.Type,
+		Type:              models.LoginType(form.Type),
 		Name:              form.AuthName,
 		IsActived:         true,
 		AllowAutoRegister: form.AllowAutoRegister,
 		Cfg:               u,
 	}
 
-	if err := models.AddSource(source); err != nil {
-		ctx.Handle(500, "admin.auths.NewAuth", err)
+	if err := models.CreateSource(source); err != nil {
+		ctx.Handle(500, "admin.auths.NewAuth(CreateSource)", err)
 		return
 	}
 
@@ -97,11 +102,11 @@ func EditAuthSource(ctx *middleware.Context, params martini.Params) {
 	}
 	u, err := models.GetLoginSourceById(id)
 	if err != nil {
-		ctx.Handle(500, "admin.user.EditUser", err)
+		ctx.Handle(500, "admin.user.EditUser(GetLoginSourceById)", err)
 		return
 	}
 	ctx.Data["Source"] = u
-	ctx.HTML(200, "admin/auths/edit")
+	ctx.HTML(200, AUTH_EDIT)
 }
 
 func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
@@ -111,13 +116,13 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
 
 	if ctx.HasError() {
-		ctx.HTML(200, "admin/auths/edit")
+		ctx.HTML(200, AUTH_EDIT)
 		return
 	}
 
 	var config core.Conversion
-	switch form.Type {
-	case models.LT_LDAP:
+	switch models.LoginType(form.Type) {
+	case models.LDAP:
 		config = &models.LDAPConfig{
 			Ldapsource: ldap.Ldapsource{
 				Host:         form.Host,
@@ -131,7 +136,7 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 				Name:         form.AuthName,
 			},
 		}
-	case models.LT_SMTP:
+	case models.SMTP:
 		config = &models.SMTPConfig{
 			Auth: form.SmtpAuth,
 			Host: form.SmtpHost,
@@ -147,13 +152,13 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 		Id:                form.Id,
 		Name:              form.AuthName,
 		IsActived:         form.IsActived,
-		Type:              form.Type,
+		Type:              models.LoginType(form.Type),
 		AllowAutoRegister: form.AllowAutoRegister,
 		Cfg:               config,
 	}
 
 	if err := models.UpdateSource(&u); err != nil {
-		ctx.Handle(500, "admin.auths.EditAuth", err)
+		ctx.Handle(500, "admin.auths.EditAuth(UpdateSource)", err)
 		return
 	}
 
@@ -175,7 +180,7 @@ func DeleteAuthSource(ctx *middleware.Context, params martini.Params) {
 
 	a, err := models.GetLoginSourceById(id)
 	if err != nil {
-		ctx.Handle(500, "admin.auths.DeleteAuth", err)
+		ctx.Handle(500, "admin.auths.DeleteAuth(GetLoginSourceById)", err)
 		return
 	}
 
@@ -185,7 +190,7 @@ func DeleteAuthSource(ctx *middleware.Context, params martini.Params) {
 			ctx.Flash.Error("This authentication still has used by some users, you should move them and then delete again.")
 			ctx.Redirect("/admin/auths/" + params["authid"])
 		default:
-			ctx.Handle(500, "admin.auths.DeleteAuth", err)
+			ctx.Handle(500, "admin.auths.DeleteAuth(DelLoginSource)", err)
 		}
 		return
 	}

+ 31 - 22
routers/admin/user.go

@@ -5,8 +5,6 @@
 package admin
 
 import (
-	"fmt"
-	"strconv"
 	"strings"
 
 	"github.com/go-martini/martini"
@@ -18,16 +16,21 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	USER_NEW  base.TplName = "admin/user/new"
+	USER_EDIT base.TplName = "admin/user/edit"
+)
+
 func NewUser(ctx *middleware.Context) {
 	ctx.Data["Title"] = "New Account"
 	ctx.Data["PageIsUsers"] = true
 	auths, err := models.GetAuths()
 	if err != nil {
-		ctx.Handle(500, "admin.user.NewUser", err)
+		ctx.Handle(500, "admin.user.NewUser(GetAuths)", err)
 		return
 	}
 	ctx.Data["LoginSources"] = auths
-	ctx.HTML(200, "admin/users/new")
+	ctx.HTML(200, USER_NEW)
 }
 
 func NewUserPost(ctx *middleware.Context, form auth.RegisterForm) {
@@ -35,7 +38,7 @@ func NewUserPost(ctx *middleware.Context, form auth.RegisterForm) {
 	ctx.Data["PageIsUsers"] = true
 
 	if ctx.HasError() {
-		ctx.HTML(200, "admin/users/new")
+		ctx.HTML(200, USER_NEW)
 		return
 	}
 
@@ -51,28 +54,29 @@ func NewUserPost(ctx *middleware.Context, form auth.RegisterForm) {
 		Email:     form.Email,
 		Passwd:    form.Password,
 		IsActive:  true,
-		LoginType: models.LT_PLAIN,
+		LoginType: models.PLAIN,
 	}
 
 	if len(form.LoginType) > 0 {
+		// NOTE: need rewrite.
 		fields := strings.Split(form.LoginType, "-")
-		u.LoginType, _ = strconv.Atoi(fields[0])
-		u.LoginSource, _ = strconv.ParseInt(fields[1], 10, 64)
+		tp, _ := base.StrTo(fields[0]).Int()
+		u.LoginType = models.LoginType(tp)
+		u.LoginSource, _ = base.StrTo(fields[1]).Int64()
 		u.LoginName = form.LoginName
-		fmt.Println(u.LoginType, u.LoginSource, u.LoginName)
 	}
 
 	var err error
-	if u, err = models.RegisterUser(u); err != nil {
+	if u, err = models.CreateUser(u); err != nil {
 		switch err {
 		case models.ErrUserAlreadyExist:
-			ctx.RenderWithErr("Username has been already taken", "admin/users/new", &form)
+			ctx.RenderWithErr("Username has been already taken", USER_NEW, &form)
 		case models.ErrEmailAlreadyUsed:
-			ctx.RenderWithErr("E-mail address has been already used", "admin/users/new", &form)
+			ctx.RenderWithErr("E-mail address has been already used", USER_NEW, &form)
 		case models.ErrUserNameIllegal:
-			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "admin/users/new", &form)
+			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), USER_NEW, &form)
 		default:
-			ctx.Handle(500, "admin.user.NewUser", err)
+			ctx.Handle(500, "admin.user.NewUser(CreateUser)", err)
 		}
 		return
 	}
@@ -95,18 +99,18 @@ func EditUser(ctx *middleware.Context, params martini.Params) {
 
 	u, err := models.GetUserById(int64(uid))
 	if err != nil {
-		ctx.Handle(500, "admin.user.EditUser", err)
+		ctx.Handle(500, "admin.user.EditUser(GetUserById)", err)
 		return
 	}
 
 	ctx.Data["User"] = u
 	auths, err := models.GetAuths()
 	if err != nil {
-		ctx.Handle(500, "admin.user.NewUser", err)
+		ctx.Handle(500, "admin.user.NewUser(GetAuths)", err)
 		return
 	}
 	ctx.Data["LoginSources"] = auths
-	ctx.HTML(200, "admin/users/edit")
+	ctx.HTML(200, USER_EDIT)
 }
 
 func EditUserPost(ctx *middleware.Context, params martini.Params, form auth.AdminEditUserForm) {
@@ -115,13 +119,18 @@ func EditUserPost(ctx *middleware.Context, params martini.Params, form auth.Admi
 
 	uid, err := base.StrTo(params["userid"]).Int()
 	if err != nil {
-		ctx.Handle(404, "admin.user.EditUser", err)
+		ctx.Handle(404, "admin.user.EditUserPost", err)
 		return
 	}
 
 	u, err := models.GetUserById(int64(uid))
 	if err != nil {
-		ctx.Handle(500, "admin.user.EditUser", err)
+		ctx.Handle(500, "admin.user.EditUserPost(GetUserById)", err)
+		return
+	}
+
+	if ctx.HasError() {
+		ctx.HTML(200, USER_EDIT)
 		return
 	}
 
@@ -133,7 +142,7 @@ func EditUserPost(ctx *middleware.Context, params martini.Params, form auth.Admi
 	u.IsActive = form.Active
 	u.IsAdmin = form.Admin
 	if err := models.UpdateUser(u); err != nil {
-		ctx.Handle(500, "admin.user.EditUser", err)
+		ctx.Handle(500, "admin.user.EditUserPost(UpdateUser)", err)
 		return
 	}
 	log.Trace("%s User profile updated by admin(%s): %s", ctx.Req.RequestURI,
@@ -151,13 +160,13 @@ func DeleteUser(ctx *middleware.Context, params martini.Params) {
 	//log.Info("delete")
 	uid, err := base.StrTo(params["userid"]).Int()
 	if err != nil {
-		ctx.Handle(404, "admin.user.EditUser", err)
+		ctx.Handle(404, "admin.user.DeleteUser", err)
 		return
 	}
 
 	u, err := models.GetUserById(int64(uid))
 	if err != nil {
-		ctx.Handle(500, "admin.user.EditUser", err)
+		ctx.Handle(500, "admin.user.DeleteUser(GetUserById)", err)
 		return
 	}
 

+ 7 - 2
routers/dashboard.go

@@ -6,11 +6,16 @@ package routers
 
 import (
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/routers/user"
 )
 
+const (
+	HOME base.TplName = "home"
+)
+
 func Home(ctx *middleware.Context) {
 	if ctx.IsSigned {
 		user.Dashboard(ctx)
@@ -26,7 +31,7 @@ func Home(ctx *middleware.Context) {
 
 	ctx.Data["PageIsHome"] = true
 
-	// Show recent updated repositoires for new visiters.
+	// Show recent updated repositories for new visitors.
 	repos, err := models.GetRecentUpdatedRepositories()
 	if err != nil {
 		ctx.Handle(500, "dashboard.Home(GetRecentUpdatedRepositories)", err)
@@ -40,7 +45,7 @@ func Home(ctx *middleware.Context) {
 		}
 	}
 	ctx.Data["Repos"] = repos
-	ctx.HTML(200, "home")
+	ctx.HTML(200, HOME)
 }
 
 func NotFound(ctx *middleware.Context) {

+ 2 - 1
routers/dev/template.go

@@ -8,6 +8,7 @@ import (
 	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/setting"
 )
@@ -22,5 +23,5 @@ func TemplatePreview(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
 	ctx.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
 	ctx.Data["CurDbValue"] = ""
-	ctx.HTML(200, params["_1"])
+	ctx.HTML(200, base.TplName(params["_1"]))
 }

+ 19 - 13
routers/install.go

@@ -14,7 +14,6 @@ import (
 	"github.com/Unknwon/goconfig"
 	"github.com/go-martini/martini"
 	"github.com/go-xorm/xorm"
-	qlog "github.com/qiniu/log"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -27,6 +26,10 @@ import (
 	"github.com/gogits/gogs/modules/social"
 )
 
+const (
+	INSTALL base.TplName = "install"
+)
+
 func checkRunMode() {
 	switch setting.Cfg.MustValue("", "RUN_MODE") {
 	case "prod":
@@ -56,11 +59,12 @@ func GlobalInit() {
 
 	if setting.InstallLock {
 		if err := models.NewEngine(); err != nil {
-			qlog.Fatal(err)
+			log.Fatal("Fail to initialize ORM engine: %v", err)
 		}
 
 		models.HasEngine = true
 		cron.NewCronContext()
+		log.NewGitLogger(path.Join(setting.LogRootPath, "http.log"))
 	}
 	if models.EnableSQLite3 {
 		log.Info("SQLite3 Enabled")
@@ -72,6 +76,7 @@ func renderDbOption(ctx *middleware.Context) {
 	ctx.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "SQLite3"}
 }
 
+// @router /install [get]
 func Install(ctx *middleware.Context, form auth.InstallForm) {
 	if setting.InstallLock {
 		ctx.Handle(404, "install.Install", errors.New("Installation is prohibited"))
@@ -119,12 +124,12 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
 	ctx.Data["CurDbOption"] = curDbOp
 
 	auth.AssignForm(form, ctx.Data)
-	ctx.HTML(200, "install")
+	ctx.HTML(200, INSTALL)
 }
 
 func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	if setting.InstallLock {
-		ctx.Handle(404, "install.Install", errors.New("Installation is prohibited"))
+		ctx.Handle(404, "install.InstallPost", errors.New("Installation is prohibited"))
 		return
 	}
 
@@ -135,12 +140,12 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	ctx.Data["CurDbOption"] = form.Database
 
 	if ctx.HasError() {
-		ctx.HTML(200, "install")
+		ctx.HTML(200, INSTALL)
 		return
 	}
 
 	if _, err := exec.LookPath("git"); err != nil {
-		ctx.RenderWithErr("Fail to test 'git' command: "+err.Error(), "install", &form)
+		ctx.RenderWithErr("Fail to test 'git' command: "+err.Error(), INSTALL, &form)
 		return
 	}
 
@@ -158,18 +163,19 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	// Set test engine.
 	var x *xorm.Engine
 	if err := models.NewTestEngine(x); err != nil {
+		// NOTE: should use core.QueryDriver (github.com/go-xorm/core)
 		if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
 			ctx.RenderWithErr("Your release version does not support SQLite3, please download the official binary version "+
-				"from http://gogs.io/docs/installation/install_from_binary.md, NOT the gobuild version.", "install", &form)
+				"from http://gogs.io/docs/installation/install_from_binary.md, NOT the gobuild version.", INSTALL, &form)
 		} else {
-			ctx.RenderWithErr("Database setting is not correct: "+err.Error(), "install", &form)
+			ctx.RenderWithErr("Database setting is not correct: "+err.Error(), INSTALL, &form)
 		}
 		return
 	}
 
 	// Test repository root path.
 	if err := os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil {
-		ctx.RenderWithErr("Repository root path is invalid: "+err.Error(), "install", &form)
+		ctx.RenderWithErr("Repository root path is invalid: "+err.Error(), INSTALL, &form)
 		return
 	}
 
@@ -180,7 +186,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	}
 	// Does not check run user when the install lock is off.
 	if form.RunUser != curUser {
-		ctx.RenderWithErr("Run user isn't the current user: "+form.RunUser+" -> "+curUser, "install", &form)
+		ctx.RenderWithErr("Run user isn't the current user: "+form.RunUser+" -> "+curUser, INSTALL, &form)
 		return
 	}
 
@@ -214,18 +220,18 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 
 	os.MkdirAll("custom/conf", os.ModePerm)
 	if err := goconfig.SaveConfigFile(setting.Cfg, path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
-		ctx.RenderWithErr("Fail to save configuration: "+err.Error(), "install", &form)
+		ctx.RenderWithErr("Fail to save configuration: "+err.Error(), INSTALL, &form)
 		return
 	}
 
 	GlobalInit()
 
 	// Create admin account.
-	if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd,
+	if _, err := models.CreateUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd,
 		IsAdmin: true, IsActive: true}); err != nil {
 		if err != models.ErrUserAlreadyExist {
 			setting.InstallLock = false
-			ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form)
+			ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), INSTALL, &form)
 			return
 		}
 		log.Info("Admin account already exist")

+ 205 - 0
routers/org/org.go

@@ -0,0 +1,205 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package org
+
+import (
+	"github.com/go-martini/martini"
+
+	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/auth"
+	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/middleware"
+	"github.com/gogits/gogs/routers/user"
+)
+
+const (
+	NEW      base.TplName = "org/new"
+	SETTINGS base.TplName = "org/settings"
+)
+
+func Organization(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Organization " + params["org"]
+	ctx.HTML(200, "org/org")
+}
+
+func Members(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Organization " + params["org"] + " Members"
+	ctx.HTML(200, "org/members")
+}
+
+func New(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Create An Organization"
+	ctx.HTML(200, NEW)
+}
+
+func NewPost(ctx *middleware.Context, form auth.CreateOrgForm) {
+	ctx.Data["Title"] = "Create An Organization"
+
+	if ctx.HasError() {
+		ctx.HTML(200, NEW)
+		return
+	}
+
+	org := &models.User{
+		Name:     form.OrgName,
+		Email:    form.Email,
+		IsActive: true, // NOTE: may need to set false when require e-mail confirmation.
+		Type:     models.ORGANIZATION,
+	}
+
+	var err error
+	if org, err = models.CreateOrganization(org, ctx.User); err != nil {
+		switch err {
+		case models.ErrUserAlreadyExist:
+			ctx.Data["Err_OrgName"] = true
+			ctx.RenderWithErr("Organization name has been already taken", NEW, &form)
+		case models.ErrEmailAlreadyUsed:
+			ctx.Data["Err_Email"] = true
+			ctx.RenderWithErr("E-mail address has been already used", NEW, &form)
+		case models.ErrUserNameIllegal:
+			ctx.Data["Err_OrgName"] = true
+			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), NEW, &form)
+		default:
+			ctx.Handle(500, "user.NewPost(CreateUser)", err)
+		}
+		return
+	}
+	log.Trace("%s Organization created: %s", ctx.Req.RequestURI, org.Name)
+
+	ctx.Redirect("/org/" + form.OrgName + "/dashboard")
+}
+
+func Dashboard(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Dashboard"
+	ctx.Data["PageIsUserDashboard"] = true
+	ctx.Data["PageIsOrgDashboard"] = true
+
+	org, err := models.GetUserByName(params["org"])
+	if err != nil {
+		if err == models.ErrUserNotExist {
+			ctx.Handle(404, "org.Dashboard(GetUserByName)", err)
+		} else {
+			ctx.Handle(500, "org.Dashboard(GetUserByName)", err)
+		}
+		return
+	}
+
+	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)
+	if err != nil {
+		ctx.Handle(500, "org.Dashboard(GetRepositories)", err)
+		return
+	}
+
+	actions, err := models.GetFeeds(org.Id, 0, false)
+	if err != nil {
+		ctx.Handle(500, "org.Dashboard(GetFeeds)", err)
+		return
+	}
+	ctx.Data["Feeds"] = actions
+
+	ctx.HTML(200, user.DASHBOARD)
+}
+
+func Settings(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Settings"
+
+	org, err := models.GetUserByName(params["org"])
+	if err != nil {
+		if err == models.ErrUserNotExist {
+			ctx.Handle(404, "org.Settings(GetUserByName)", err)
+		} else {
+			ctx.Handle(500, "org.Settings(GetUserByName)", err)
+		}
+		return
+	}
+	ctx.Data["Org"] = org
+
+	ctx.HTML(200, SETTINGS)
+}
+
+func SettingsPost(ctx *middleware.Context, params martini.Params, form auth.OrgSettingForm) {
+	ctx.Data["Title"] = "Settings"
+
+	org, err := models.GetUserByName(params["org"])
+	if err != nil {
+		if err == models.ErrUserNotExist {
+			ctx.Handle(404, "org.SettingsPost(GetUserByName)", err)
+		} else {
+			ctx.Handle(500, "org.SettingsPost(GetUserByName)", err)
+		}
+		return
+	}
+	ctx.Data["Org"] = org
+
+	if ctx.HasError() {
+		ctx.HTML(200, SETTINGS)
+		return
+	}
+
+	org.FullName = form.DisplayName
+	org.Email = form.Email
+	org.Description = form.Description
+	org.Website = form.Website
+	org.Location = form.Location
+	if err = models.UpdateUser(org); err != nil {
+		ctx.Handle(500, "org.SettingsPost(UpdateUser)", err)
+		return
+	}
+	log.Trace("%s Organization setting updated: %s", ctx.Req.RequestURI, org.LowerName)
+	ctx.Flash.Success("Organization profile has been successfully updated.")
+	ctx.Redirect("/org/" + org.Name + "/settings")
+}
+
+func DeletePost(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Settings"
+
+	org, err := models.GetUserByName(params["org"])
+	if err != nil {
+		if err == models.ErrUserNotExist {
+			ctx.Handle(404, "org.DeletePost(GetUserByName)", err)
+		} else {
+			ctx.Handle(500, "org.DeletePost(GetUserByName)", err)
+		}
+		return
+	}
+	ctx.Data["Org"] = org
+
+	if !models.IsOrganizationOwner(org.Id, ctx.User.Id) {
+		ctx.Error(403)
+		return
+	}
+
+	tmpUser := models.User{
+		Passwd: ctx.Query("password"),
+		Salt:   ctx.User.Salt,
+	}
+	tmpUser.EncodePasswd()
+	if tmpUser.Passwd != ctx.User.Passwd {
+		ctx.Flash.Error("Password is not correct. Make sure you are owner of this account.")
+	} else {
+		if err := models.DeleteOrganization(org); err != nil {
+			switch err {
+			case models.ErrUserOwnRepos:
+				ctx.Flash.Error("This organization still have ownership of repository, you have to delete or transfer them first.")
+			default:
+				ctx.Handle(500, "org.DeletePost(DeleteOrganization)", err)
+				return
+			}
+		} else {
+			ctx.Redirect("/")
+			return
+		}
+	}
+
+	ctx.Redirect("/org/" + org.Name + "/settings")
+}

+ 21 - 0
routers/org/teams.go

@@ -0,0 +1,21 @@
+package org
+
+import (
+	"github.com/go-martini/martini"
+	"github.com/gogits/gogs/modules/middleware"
+)
+
+func Teams(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Organization "+params["org"]+" Teams"
+	ctx.HTML(200, "org/teams")
+}
+
+func NewTeam(ctx *middleware.Context, params martini.Params) {
+	ctx.Data["Title"] = "Organization "+params["org"]+" New Team"
+	ctx.HTML(200, "org/new_team")
+}
+
+func EditTeam(ctx *middleware.Context, params martini.Params){
+	ctx.Data["Title"] = "Organization "+params["org"]+" Edit Team"
+	ctx.HTML(200,"org/edit_team")
+}

+ 8 - 3
routers/repo/branch.go

@@ -7,22 +7,27 @@ package repo
 import (
 	"github.com/go-martini/martini"
 
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	BRANCH base.TplName = "repo/branch"
+)
+
 func Branches(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["Title"] = "Branches"
 	ctx.Data["IsRepoToolbarBranches"] = true
 
 	brs, err := ctx.Repo.GitRepo.GetBranches()
 	if err != nil {
-		ctx.Handle(500, "repo.Branches", err)
+		ctx.Handle(500, "repo.Branches(GetBranches)", err)
 		return
 	} else if len(brs) == 0 {
-		ctx.Handle(404, "repo.Branches", nil)
+		ctx.Handle(404, "repo.Branches(GetBranches)", nil)
 		return
 	}
 
 	ctx.Data["Branches"] = brs
-	ctx.HTML(200, "repo/branches")
+	ctx.HTML(200, BRANCH)
 }

+ 48 - 44
routers/repo/commit.go

@@ -14,6 +14,11 @@ import (
 	"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
 
@@ -22,10 +27,10 @@ func Commits(ctx *middleware.Context, params martini.Params) {
 
 	brs, err := ctx.Repo.GitRepo.GetBranches()
 	if err != nil {
-		ctx.Handle(500, "repo.Commits", err)
+		ctx.Handle(500, "repo.Commits(GetBranches)", err)
 		return
 	} else if len(brs) == 0 {
-		ctx.Handle(404, "repo.Commits", nil)
+		ctx.Handle(404, "repo.Commits(GetBranches)", nil)
 		return
 	}
 
@@ -61,7 +66,43 @@ func Commits(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["LastPageNum"] = lastPage
 	ctx.Data["NextPageNum"] = nextPage
-	ctx.HTML(200, "repo/commits")
+	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) {
@@ -75,7 +116,7 @@ func Diff(ctx *middleware.Context, params martini.Params) {
 
 	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
 	if err != nil {
-		ctx.Handle(404, "repo.Diff", err)
+		ctx.Handle(404, "repo.Diff(GetDiff)", err)
 		return
 	}
 
@@ -119,43 +160,7 @@ func Diff(ctx *middleware.Context, params martini.Params) {
 	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, "repo/diff")
-}
-
-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, "repo/commits")
+	ctx.HTML(200, DIFF)
 }
 
 func FileHistory(ctx *middleware.Context, params martini.Params) {
@@ -184,8 +189,7 @@ func FileHistory(ctx *middleware.Context, params martini.Params) {
 	if err != nil {
 		ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
 		return
-	}
-	if commitsCount == 0 {
+	} else if commitsCount == 0 {
 		ctx.Handle(404, "repo.FileHistory", nil)
 		return
 	}
@@ -217,5 +221,5 @@ func FileHistory(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["LastPageNum"] = lastPage
 	ctx.Data["NextPageNum"] = nextPage
-	ctx.HTML(200, "repo/commits")
+	ctx.HTML(200, COMMITS)
 }

+ 20 - 31
routers/repo/http.go

@@ -7,9 +7,7 @@ package repo
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"io/ioutil"
-	"log"
 	"net/http"
 	"os"
 	"os/exec"
@@ -22,6 +20,7 @@ import (
 
 	"github.com/go-martini/martini"
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/setting"
 )
@@ -107,9 +106,9 @@ func Http(ctx *middleware.Context, params martini.Params) {
 		}
 
 		if !isPublicPull {
-			var tp = models.AU_WRITABLE
+			var tp = models.WRITABLE
 			if isPull {
-				tp = models.AU_READABLE
+				tp = models.READABLE
 			}
 
 			has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
@@ -117,8 +116,8 @@ func Http(ctx *middleware.Context, params martini.Params) {
 				ctx.Handle(401, "no basic auth and digit auth", nil)
 				return
 			} else if !has {
-				if tp == models.AU_READABLE {
-					has, err = models.HasAccess(authUsername, username+"/"+reponame, models.AU_WRITABLE)
+				if tp == models.READABLE {
+					has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE)
 					if err != nil || !has {
 						ctx.Handle(401, "no basic auth and digit auth", nil)
 						return
@@ -141,7 +140,10 @@ func Http(ctx *middleware.Context, params martini.Params) {
 					newCommitId := fields[1]
 					refName := fields[2]
 
-					models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id)
+					if err = models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id); err != nil {
+						log.GitLogger.Error(err.Error())
+						return
+					}
 				}
 			}
 		}
@@ -190,7 +192,6 @@ var routes = []route{
 // Request handling function
 func HttpBackend(config *Config) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
-		//log.Printf("%s %s %s %s", r.RemoteAddr, r.Method, r.URL.Path, r.Proto)
 		for _, route := range routes {
 			if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
 				if route.method != r.Method {
@@ -202,7 +203,7 @@ func HttpBackend(config *Config) http.HandlerFunc {
 				dir, err := getGitDir(config, m[1])
 
 				if err != nil {
-					log.Print(err)
+					log.GitLogger.Error(err.Error())
 					renderNotFound(w)
 					return
 				}
@@ -212,13 +213,13 @@ func HttpBackend(config *Config) http.HandlerFunc {
 				return
 			}
 		}
+
 		renderNotFound(w)
 		return
 	}
 }
 
 // Actual command handling functions
-
 func serviceUploadPack(hr handler) {
 	serviceRpc("upload-pack", hr)
 }
@@ -236,36 +237,24 @@ func serviceRpc(rpc string, hr handler) {
 		return
 	}
 
-	input, _ := ioutil.ReadAll(r.Body)
-
 	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
 	w.WriteHeader(http.StatusOK)
 
+	input, _ := ioutil.ReadAll(r.Body)
+	br := bytes.NewReader(input)
+
 	args := []string{rpc, "--stateless-rpc", dir}
 	cmd := exec.Command(hr.Config.GitBinPath, args...)
 	cmd.Dir = dir
-	in, err := cmd.StdinPipe()
-	if err != nil {
-		log.Print(err)
-		return
-	}
-
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		log.Print(err)
-		return
-	}
+	cmd.Stdout = w
+	cmd.Stdin = br
 
-	err = cmd.Start()
+	err := cmd.Run()
 	if err != nil {
-		log.Print(err)
+		log.GitLogger.Error(err.Error())
 		return
 	}
 
-	in.Write(input)
-	io.Copy(w, stdout)
-	cmd.Wait()
-
 	if hr.Config.OnSucceed != nil {
 		hr.Config.OnSucceed(rpc, input)
 	}
@@ -345,7 +334,7 @@ func getGitDir(config *Config, fPath string) (string, error) {
 		cwd, err := os.Getwd()
 
 		if err != nil {
-			log.Print(err)
+			log.GitLogger.Error(err.Error())
 			return "", err
 		}
 
@@ -422,7 +411,7 @@ func gitCommand(gitBinPath, dir string, args ...string) []byte {
 	out, err := command.Output()
 
 	if err != nil {
-		log.Print(err)
+		log.GitLogger.Error(err.Error())
 	}
 
 	return out

+ 29 - 9
routers/repo/issue.go

@@ -22,6 +22,16 @@ import (
 	"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"
+)
+
 func Issues(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Issues"
 	ctx.Data["IsRepoToolbarIssues"] = true
@@ -134,7 +144,7 @@ func Issues(ctx *middleware.Context) {
 	} else {
 		ctx.Data["ShowCount"] = issueStats.OpenCount
 	}
-	ctx.HTML(200, "issue/list")
+	ctx.HTML(200, ISSUES)
 }
 
 func CreateIssue(ctx *middleware.Context, params martini.Params) {
@@ -161,7 +171,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 	ctx.Data["Collaborators"] = us
-	ctx.HTML(200, "issue/create")
+	ctx.HTML(200, ISSUE_CREATE)
 }
 
 func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
@@ -190,7 +200,7 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
 	ctx.Data["Collaborators"] = us
 
 	if ctx.HasError() {
-		ctx.HTML(200, "issue/create")
+		ctx.HTML(200, ISSUE_CREATE)
 		return
 	}
 
@@ -250,7 +260,7 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
 	}
 
 	// Mail watchers and mentions.
-	if setting.Service.NotifyMail {
+	if setting.Service.EnableNotifyMail {
 		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
 		if err != nil {
 			ctx.Handle(500, "issue.CreateIssue(SendIssueNotifyMail)", err)
@@ -392,7 +402,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 	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")
+	ctx.HTML(200, ISSUE_VIEW)
 }
 
 func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
@@ -685,7 +695,7 @@ func Comment(ctx *middleware.Context, params martini.Params) {
 	}
 
 	// Mail watchers and mentions.
-	if setting.Service.NotifyMail {
+	if setting.Service.EnableNotifyMail {
 		issue.Content = content
 		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
 		if err != nil {
@@ -794,14 +804,14 @@ func Milestones(ctx *middleware.Context) {
 	} else {
 		ctx.Data["State"] = "open"
 	}
-	ctx.HTML(200, "issue/milestone")
+	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, "issue/milestone_new")
+	ctx.HTML(200, MILESTONE_NEW)
 }
 
 func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
@@ -809,6 +819,11 @@ func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
 	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 {
@@ -890,7 +905,7 @@ func UpdateMilestone(ctx *middleware.Context, params martini.Params) {
 	}
 	ctx.Data["Milestone"] = mile
 
-	ctx.HTML(200, "issue/milestone_edit")
+	ctx.HTML(200, MILESTONE_EDIT)
 }
 
 func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form auth.CreateMilestoneForm) {
@@ -914,6 +929,11 @@ func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form au
 		return
 	}
 
+	if ctx.HasError() {
+		ctx.HTML(200, MILESTONE_EDIT)
+		return
+	}
+
 	var deadline time.Time
 	if len(form.Deadline) == 0 {
 		form.Deadline = "12/31/9999"

+ 6 - 1
routers/repo/pull.go

@@ -7,10 +7,15 @@ package repo
 import (
 	"github.com/go-martini/martini"
 
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	PULLS base.TplName = "repo/pulls"
+)
+
 func Pulls(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["IsRepoToolbarPulls"] = true
-	ctx.HTML(200, "repo/pulls")
+	ctx.HTML(200, PULLS)
 }

+ 118 - 38
routers/repo/release.go

@@ -5,7 +5,7 @@
 package repo
 
 import (
-	"sort"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -14,21 +14,11 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
-type ReleaseSorter struct {
-	rels []*models.Release
-}
-
-func (rs *ReleaseSorter) Len() int {
-	return len(rs.rels)
-}
-
-func (rs *ReleaseSorter) Less(i, j int) bool {
-	return rs.rels[i].NumCommits > rs.rels[j].NumCommits
-}
-
-func (rs *ReleaseSorter) Swap(i, j int) {
-	rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
-}
+const (
+	RELEASES     base.TplName = "repo/release/list"
+	RELEASE_NEW  base.TplName = "repo/release/new"
+	RELEASE_EDIT base.TplName = "repo/release/edit"
+)
 
 func Releases(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Releases"
@@ -52,65 +42,88 @@ func Releases(ctx *middleware.Context) {
 		return
 	}
 
-	var tags ReleaseSorter
-	tags.rels = make([]*models.Release, len(rawTags))
+	// 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
 				}
-				rel.NumCommitsBehind = commitsCount - rel.NumCommits
+				// 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.rels[i] = rel
+				tags[i] = rel
 				break
 			}
 		}
 
-		if tags.rels[i] == nil {
+		if tags[i] == nil {
 			commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag)
 			if err != nil {
-				ctx.Handle(500, "release.Releases(GetCommitOfTag)", err)
+				ctx.Handle(500, "release.Releases(GetCommitOfTag2)", err)
 				return
 			}
 
-			tags.rels[i] = &models.Release{
+			tags[i] = &models.Release{
 				Title:   rawTag,
 				TagName: rawTag,
-				SHA1:    commit.Id.String(),
+				Sha1:    commit.Id.String(),
 			}
-			tags.rels[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(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.rels[i].NumCommitsBehind = commitsCount - tags.rels[i].NumCommits
+			tags[i].NumCommitsBehind = commitsCount - tags[i].NumCommits
 		}
 	}
-
-	sort.Sort(&tags)
-
-	ctx.Data["Releases"] = tags.rels
-	ctx.HTML(200, "release/list")
+	models.SortReleases(tags)
+	ctx.Data["Releases"] = tags
+	ctx.HTML(200, RELEASES)
 }
 
-func ReleasesNew(ctx *middleware.Context) {
+func NewRelease(ctx *middleware.Context) {
 	if !ctx.Repo.IsOwner {
-		ctx.Handle(404, "release.ReleasesNew", nil)
+		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")
+	ctx.HTML(200, RELEASE_NEW)
 }
 
-func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
+func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
 	if !ctx.Repo.IsOwner {
-		ctx.Handle(404, "release.ReleasesNew", nil)
+		ctx.Handle(403, "release.ReleasesNew", nil)
 		return
 	}
 
@@ -119,7 +132,7 @@ func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
 	ctx.Data["IsRepoReleaseNew"] = true
 
 	if ctx.HasError() {
-		ctx.HTML(200, "release/new")
+		ctx.HTML(200, RELEASE_NEW)
 		return
 	}
 
@@ -129,14 +142,21 @@ func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
 		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,
-		SHA1:         ctx.Repo.Commit.Id.String(),
+		Target:       form.Target,
+		Sha1:         ctx.Repo.Commit.Id.String(),
 		NumCommits:   commitsCount,
 		Note:         form.Content,
+		IsDraft:      len(form.Draft) > 0,
 		IsPrerelease: form.Prerelease,
 	}
 
@@ -152,3 +172,63 @@ func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
 
 	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")
+}

+ 84 - 22
routers/repo/repo.go

@@ -25,12 +25,25 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	CREATE  base.TplName = "repo/create"
+	MIGRATE base.TplName = "repo/migrate"
+	SINGLE  base.TplName = "repo/single"
+)
+
 func Create(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Create repository"
 	ctx.Data["PageIsNewRepo"] = true
 	ctx.Data["LanguageIgns"] = models.LanguageIgns
 	ctx.Data["Licenses"] = models.Licenses
-	ctx.HTML(200, "repo/create")
+
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["Orgs"] = ctx.User.Orgs
+
+	ctx.HTML(200, CREATE)
 }
 
 func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
@@ -39,76 +52,125 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
 	ctx.Data["LanguageIgns"] = models.LanguageIgns
 	ctx.Data["Licenses"] = models.Licenses
 
+	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, "repo/create")
+		ctx.HTML(200, CREATE)
 		return
 	}
 
-	repo, err := models.CreateRepository(ctx.User, form.RepoName, form.Description,
+	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.CreatePost(GetUserById)", err)
+			} else {
+				ctx.Handle(500, "home.CreatePost(GetUserById)", err)
+			}
+			return
+		}
+	}
+
+	repo, err := models.CreateRepository(u, form.RepoName, form.Description,
 		form.Language, form.License, form.Private, false, form.InitReadme)
 	if err == nil {
-		log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName)
-		ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName)
+		log.Trace("%s Repository created: %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", "repo/create", &form)
+		ctx.RenderWithErr("Repository name has already been used", CREATE, &form)
 		return
 	} else if err == models.ErrRepoNameIllegal {
-		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form)
+		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), CREATE, &form)
 		return
 	}
 
 	if repo != nil {
-		if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil {
-			log.Error("repo.MigratePost(CreatePost): %v", errDelete)
+		if errDelete := models.DeleteRepository(u.Id, repo.Id, u.Name); errDelete != nil {
+			log.Error("repo.CreatePost(DeleteRepository): %v", errDelete)
 		}
 	}
-	ctx.Handle(500, "repo.Create", err)
+	ctx.Handle(500, "repo.CreatePost(CreateRepository)", err)
 }
 
 func Migrate(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Migrate repository"
 	ctx.Data["PageIsNewRepo"] = true
-	ctx.HTML(200, "repo/migrate")
+
+	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)
 }
 
 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 ctx.HasError() {
-		ctx.HTML(200, "repo/migrate")
+		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
+		}
+	}
+
 	authStr := strings.Replace(fmt.Sprintf("://%s:%s",
 		form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
 	url := strings.Replace(form.Url, "://", authStr+"@", 1)
-	repo, err := models.MigrateRepository(ctx.User, form.RepoName, form.Description, form.Private,
+	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, ctx.User.LowerName, form.RepoName)
-		ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName)
+		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", "repo/migrate", &form)
+		ctx.RenderWithErr("Repository name has already been used", MIGRATE, &form)
 		return
 	} else if err == models.ErrRepoNameIllegal {
-		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/migrate", &form)
+		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), MIGRATE, &form)
 		return
 	}
 
 	if repo != nil {
-		if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil {
+		if errDelete := models.DeleteRepository(u.Id, repo.Id, u.Name); errDelete != nil {
 			log.Error("repo.MigratePost(DeleteRepository): %v", errDelete)
 		}
 	}
 
 	if strings.Contains(err.Error(), "Authentication failed") {
-		ctx.RenderWithErr(err.Error(), "repo/migrate", &form)
+		ctx.RenderWithErr(err.Error(), MIGRATE, &form)
 		return
 	}
-	ctx.Handle(500, "repo.Migrate", err)
+	ctx.Handle(500, "repo.Migrate(MigrateRepository)", err)
 }
 
 func Single(ctx *middleware.Context, params martini.Params) {
@@ -291,7 +353,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["Treenames"] = treenames
 	ctx.Data["TreePath"] = treePath
 	ctx.Data["BranchLink"] = branchLink
-	ctx.HTML(200, "repo/single")
+	ctx.HTML(200, SINGLE)
 }
 
 func basicEncode(username, password string) string {
@@ -318,7 +380,7 @@ func basicDecode(encoded string) (user string, name string, err error) {
 func authRequired(ctx *middleware.Context) {
 	ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
 	ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
-	ctx.HTML(401, fmt.Sprintf("status/401"))
+	ctx.HTML(401, base.TplName("status/401"))
 }
 
 func Action(ctx *middleware.Context, params martini.Params) {

+ 68 - 41
routers/repo/setting.go

@@ -7,6 +7,7 @@ package repo
 import (
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/go-martini/martini"
 
@@ -19,10 +20,19 @@ import (
 	"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, "repo/setting")
+	ctx.HTML(200, SETTING)
 }
 
 func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
@@ -31,7 +41,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 	switch ctx.Query("action") {
 	case "update":
 		if ctx.HasError() {
-			ctx.HTML(200, "repo/setting")
+			ctx.HTML(200, SETTING)
 			return
 		}
 
@@ -43,7 +53,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 				ctx.Handle(500, "setting.SettingPost(update: check existence)", err)
 				return
 			} else if isExist {
-				ctx.RenderWithErr("Repository name has been taken in your repositories.", "repo/setting", nil)
+				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)
@@ -72,6 +82,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 		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)
 				}
@@ -82,7 +93,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 		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.", "repo/setting", nil)
+			ctx.RenderWithErr("Please make sure you entered repository name is correct.", SETTING, nil)
 			return
 		} else if ctx.Repo.Repository.IsMirror {
 			ctx.Error(404)
@@ -96,7 +107,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 			ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err)
 			return
 		} else if !isExist {
-			ctx.RenderWithErr("Please make sure you entered owner name is correct.", "repo/setting", nil)
+			ctx.RenderWithErr("Please make sure you entered owner name is correct.", SETTING, nil)
 			return
 		} else if err = models.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository); err != nil {
 			ctx.Handle(500, "setting.SettingPost(transfer repository)", err)
@@ -107,17 +118,27 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 		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.", "repo/setting", nil)
+			ctx.RenderWithErr("Please make sure you entered repository name is correct.", SETTING, nil)
 			return
 		}
 
-		if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
-			ctx.Handle(500, "setting.Delete", err)
+		if ctx.Repo.Owner.IsOrganization() &&
+			!models.IsOrganizationOwner(ctx.Repo.Owner.Id, ctx.User.Id) {
+			ctx.Error(403)
 			return
 		}
-		log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
 
-		ctx.Redirect("/")
+		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("/")
+		}
 	}
 }
 
@@ -154,7 +175,7 @@ func Collaboration(ctx *middleware.Context) {
 	}
 
 	ctx.Data["Collaborators"] = us
-	ctx.HTML(200, "repo/collaboration")
+	ctx.HTML(200, COLLABORATION)
 }
 
 func CollaborationPost(ctx *middleware.Context) {
@@ -164,7 +185,7 @@ func CollaborationPost(ctx *middleware.Context) {
 		ctx.Redirect(ctx.Req.RequestURI)
 		return
 	}
-	has, err := models.HasAccess(name, repoLink, models.AU_WRITABLE)
+	has, err := models.HasAccess(name, repoLink, models.WRITABLE)
 	if err != nil {
 		ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err)
 		return
@@ -185,12 +206,12 @@ func CollaborationPost(ctx *middleware.Context) {
 	}
 
 	if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink,
-		Mode: models.AU_WRITABLE}); err != nil {
+		Mode: models.WRITABLE}); err != nil {
 		ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err)
 		return
 	}
 
-	if setting.Service.NotifyMail {
+	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
@@ -224,13 +245,13 @@ func WebHooks(ctx *middleware.Context) {
 	}
 
 	ctx.Data["Webhooks"] = ws
-	ctx.HTML(200, "repo/hooks")
+	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, "repo/hooks_add")
+	ctx.HTML(200, HOOK_ADD)
 }
 
 func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) {
@@ -238,13 +259,13 @@ func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
 
 	if ctx.HasError() {
-		ctx.HTML(200, "repo/hooks_add")
+		ctx.HTML(200, HOOK_ADD)
 		return
 	}
 
-	ct := models.CT_JSON
+	ct := models.JSON
 	if form.ContentType == "2" {
-		ct = models.CT_FORM
+		ct = models.FORM
 	}
 
 	w := &models.Webhook{
@@ -257,8 +278,8 @@ func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 		},
 		IsActive: form.Active,
 	}
-	if err := w.SaveEvent(); err != nil {
-		ctx.Handle(500, "setting.WebHooksAddPost(SaveEvent)", err)
+	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)
@@ -291,42 +312,48 @@ func WebHooksEdit(ctx *middleware.Context, params martini.Params) {
 
 	w.GetEvent()
 	ctx.Data["Webhook"] = w
-	ctx.HTML(200, "repo/hooks_edit")
+	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"
 
-	if ctx.HasError() {
-		ctx.HTML(200, "repo/hooks_add")
-		return
-	}
-
 	hookId, _ := base.StrTo(params["id"]).Int64()
 	if hookId == 0 {
 		ctx.Handle(404, "setting.WebHooksEditPost", nil)
 		return
 	}
 
-	ct := models.CT_JSON
+	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.CT_FORM
+		ct = models.FORM
 	}
 
-	w := &models.Webhook{
-		Id:          hookId,
-		RepoId:      ctx.Repo.Repository.Id,
-		Url:         form.Url,
-		ContentType: ct,
-		Secret:      form.Secret,
-		HookEvent: &models.HookEvent{
-			PushOnly: form.PushOnly,
-		},
-		IsActive: form.Active,
+	w.Url = form.Url
+	w.ContentType = ct
+	w.Secret = form.Secret
+	w.HookEvent = &models.HookEvent{
+		PushOnly: form.PushOnly,
 	}
-	if err := w.SaveEvent(); err != nil {
-		ctx.Handle(500, "setting.WebHooksEditPost(SaveEvent)", err)
+	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)

+ 30 - 11
routers/user/home.go

@@ -17,10 +17,25 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	DASHBOARD base.TplName = "user/dashboard"
+	PROFILE   base.TplName = "user/profile"
+	ISSUES    base.TplName = "user/issues"
+	PULLS     base.TplName = "user/pulls"
+	STARS     base.TplName = "user/stars"
+)
+
 func Dashboard(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Dashboard"
 	ctx.Data["PageIsUserDashboard"] = true
 
+	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
+
 	var err error
 	ctx.Data["MyRepos"], err = models.GetRepositories(ctx.User.Id, true)
 	if err != nil {
@@ -45,21 +60,21 @@ func Dashboard(ctx *middleware.Context) {
 	for _, act := range actions {
 		if act.IsPrivate {
 			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
-				models.AU_READABLE); !has {
+				models.READABLE); !has {
 				continue
 			}
 		}
 		feeds = append(feeds, act)
 	}
 	ctx.Data["Feeds"] = feeds
-	ctx.HTML(200, "user/dashboard")
+	ctx.HTML(200, DASHBOARD)
 }
 
 func Profile(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["Title"] = "Profile"
 	ctx.Data["PageIsUserProfile"] = true
 
-	user, err := models.GetUserByName(params["username"])
+	u, err := models.GetUserByName(params["username"])
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "user.Profile(GetUserByName)", err)
@@ -68,26 +83,30 @@ func Profile(ctx *middleware.Context, params martini.Params) {
 		}
 		return
 	}
-	ctx.Data["Owner"] = user
+	// For security reason, hide e-mail address for anonymous visitors.
+	if !ctx.IsSigned {
+		u.Email = ""
+	}
+	ctx.Data["Owner"] = u
 
 	tab := ctx.Query("tab")
 	ctx.Data["TabName"] = tab
 	switch tab {
 	case "activity":
-		ctx.Data["Feeds"], err = models.GetFeeds(user.Id, 0, true)
+		ctx.Data["Feeds"], err = models.GetFeeds(u.Id, 0, true)
 		if err != nil {
 			ctx.Handle(500, "user.Profile(GetFeeds)", err)
 			return
 		}
 	default:
-		ctx.Data["Repos"], err = models.GetRepositories(user.Id, ctx.IsSigned && ctx.User.Id == user.Id)
+		ctx.Data["Repos"], err = models.GetRepositories(u.Id, ctx.IsSigned && ctx.User.Id == u.Id)
 		if err != nil {
 			ctx.Handle(500, "user.Profile(GetRepositories)", err)
 			return
 		}
 	}
 
-	ctx.HTML(200, "user/profile")
+	ctx.HTML(200, PROFILE)
 }
 
 func Email2User(ctx *middleware.Context) {
@@ -119,7 +138,7 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
 	for _, act := range actions {
 		if act.IsPrivate {
 			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
-				models.AU_READABLE); !has {
+				models.READABLE); !has {
 				continue
 			}
 		}
@@ -254,13 +273,13 @@ func Issues(ctx *middleware.Context) {
 	} else {
 		ctx.Data["ShowCount"] = issueStats.OpenCount
 	}
-	ctx.HTML(200, "user/issue")
+	ctx.HTML(200, ISSUES)
 }
 
 func Pulls(ctx *middleware.Context) {
-	ctx.HTML(200, "user/pulls")
+	ctx.HTML(200, PULLS)
 }
 
 func Stars(ctx *middleware.Context) {
-	ctx.HTML(200, "user/stars")
+	ctx.HTML(200, STARS)
 }

+ 18 - 9
routers/user/setting.go

@@ -14,12 +14,21 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
+const (
+	SETTING      base.TplName = "user/setting"
+	SOCIAL       base.TplName = "user/social"
+	PASSWORD     base.TplName = "user/password"
+	PUBLICKEY    base.TplName = "user/publickey"
+	NOTIFICATION base.TplName = "user/notification"
+	SECURITY     base.TplName = "user/security"
+)
+
 func Setting(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Setting"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSetting"] = true
 	ctx.Data["Owner"] = ctx.User
-	ctx.HTML(200, "user/setting")
+	ctx.HTML(200, SETTING)
 }
 
 func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
@@ -28,7 +37,7 @@ func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
 	ctx.Data["IsUserPageSetting"] = true
 
 	if ctx.HasError() {
-		ctx.HTML(200, "user/setting")
+		ctx.HTML(200, SETTING)
 		return
 	}
 
@@ -59,7 +68,7 @@ func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
 	ctx.User.Avatar = base.EncodeMd5(form.Avatar)
 	ctx.User.AvatarEmail = form.Avatar
 	if err := models.UpdateUser(ctx.User); err != nil {
-		ctx.Handle(500, "setting.Setting", err)
+		ctx.Handle(500, "setting.Setting(UpdateUser)", err)
 		return
 	}
 	log.Trace("%s User setting updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
@@ -90,14 +99,14 @@ func SettingSocial(ctx *middleware.Context) {
 		ctx.Handle(500, "user.SettingSocial(GetOauthByUserId)", err)
 		return
 	}
-	ctx.HTML(200, "user/social")
+	ctx.HTML(200, SOCIAL)
 }
 
 func SettingPassword(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Password"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingPasswd"] = true
-	ctx.HTML(200, "user/password")
+	ctx.HTML(200, PASSWORD)
 }
 
 func SettingPasswordPost(ctx *middleware.Context, form auth.UpdatePasswdForm) {
@@ -106,7 +115,7 @@ func SettingPasswordPost(ctx *middleware.Context, form auth.UpdatePasswdForm) {
 	ctx.Data["IsUserPageSettingPasswd"] = true
 
 	if ctx.HasError() {
-		ctx.HTML(200, "user/password")
+		ctx.HTML(200, PASSWORD)
 		return
 	}
 
@@ -207,7 +216,7 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) {
 		}
 	}
 
-	ctx.HTML(200, "user/publickey")
+	ctx.HTML(200, PUBLICKEY)
 }
 
 func SettingNotification(ctx *middleware.Context) {
@@ -215,7 +224,7 @@ func SettingNotification(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Notification"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingNotify"] = true
-	ctx.HTML(200, "user/notification")
+	ctx.HTML(200, NOTIFICATION)
 }
 
 func SettingSecurity(ctx *middleware.Context) {
@@ -223,5 +232,5 @@ func SettingSecurity(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Security"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingSecurity"] = true
-	ctx.HTML(200, "user/security")
+	ctx.HTML(200, SECURITY)
 }

+ 45 - 33
routers/user/user.go

@@ -17,12 +17,21 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
+const (
+	SIGNIN          base.TplName = "user/signin"
+	SIGNUP          base.TplName = "user/signup"
+	DELETE          base.TplName = "user/delete"
+	ACTIVATE        base.TplName = "user/activate"
+	FORGOT_PASSWORD base.TplName = "user/forgot_passwd"
+	RESET_PASSWORD  base.TplName = "user/reset_passwd"
+)
+
 func SignIn(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Log In"
 
 	if _, ok := ctx.Session.Get("socialId").(int64); ok {
 		ctx.Data["IsSocialLogin"] = true
-		ctx.HTML(200, "user/signin")
+		ctx.HTML(200, SIGNIN)
 		return
 	}
 
@@ -32,23 +41,23 @@ func SignIn(ctx *middleware.Context) {
 	}
 
 	// Check auto-login.
-	userName := ctx.GetCookie(setting.CookieUserName)
-	if len(userName) == 0 {
-		ctx.HTML(200, "user/signin")
+	uname := ctx.GetCookie(setting.CookieUserName)
+	if len(uname) == 0 {
+		ctx.HTML(200, SIGNIN)
 		return
 	}
 
 	isSucceed := false
 	defer func() {
 		if !isSucceed {
-			log.Trace("user.SignIn(auto-login cookie cleared): %s", userName)
+			log.Trace("user.SignIn(auto-login cookie cleared): %s", uname)
 			ctx.SetCookie(setting.CookieUserName, "", -1)
 			ctx.SetCookie(setting.CookieRememberName, "", -1)
 			return
 		}
 	}()
 
-	user, err := models.GetUserByName(userName)
+	user, err := models.GetUserByName(uname)
 	if err != nil {
 		ctx.Handle(500, "user.SignIn(GetUserByName)", err)
 		return
@@ -57,7 +66,7 @@ func SignIn(ctx *middleware.Context) {
 	secret := base.EncodeMd5(user.Rands + user.Passwd)
 	value, _ := ctx.GetSecureCookie(secret, setting.CookieRememberName)
 	if value != user.Name {
-		ctx.HTML(200, "user/signin")
+		ctx.HTML(200, SIGNIN)
 		return
 	}
 
@@ -86,19 +95,19 @@ func SignInPost(ctx *middleware.Context, form auth.LogInForm) {
 	}
 
 	if ctx.HasError() {
-		ctx.HTML(200, "user/signin")
+		ctx.HTML(200, SIGNIN)
 		return
 	}
 
-	user, err := models.LoginUser(form.UserName, form.Password)
+	user, err := models.UserSignIn(form.UserName, form.Password)
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			log.Trace("%s Log in failed: %s", ctx.Req.RequestURI, form.UserName)
-			ctx.RenderWithErr("Username or password is not correct", "user/signin", &form)
+			ctx.RenderWithErr("Username or password is not correct", SIGNIN, &form)
 			return
 		}
 
-		ctx.Handle(500, "user.SignIn", err)
+		ctx.Handle(500, "user.SignInPost(UserSignIn)", err)
 		return
 	}
 
@@ -151,7 +160,7 @@ func SignUp(ctx *middleware.Context) {
 
 	if setting.Service.DisableRegistration {
 		ctx.Data["DisableRegistration"] = true
-		ctx.HTML(200, "user/signup")
+		ctx.HTML(200, SIGNUP)
 		return
 	}
 
@@ -160,7 +169,7 @@ func SignUp(ctx *middleware.Context) {
 		return
 	}
 
-	ctx.HTML(200, "user/signup")
+	ctx.HTML(200, SIGNUP)
 }
 
 func oauthSignUp(ctx *middleware.Context, sid int64) {
@@ -180,7 +189,7 @@ func oauthSignUp(ctx *middleware.Context, sid int64) {
 	ctx.Data["username"] = strings.Replace(ctx.Session.Get("socialName").(string), " ", "", -1)
 	ctx.Data["email"] = ctx.Session.Get("socialEmail")
 	log.Trace("user.oauthSignUp(social ID): %v", ctx.Session.Get("socialId"))
-	ctx.HTML(200, "user/signup")
+	ctx.HTML(200, SIGNUP)
 }
 
 func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
@@ -198,14 +207,14 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
 	}
 
 	if ctx.HasError() {
-		ctx.HTML(200, "user/signup")
+		ctx.HTML(200, SIGNUP)
 		return
 	}
 
 	if form.Password != form.RetypePasswd {
 		ctx.Data["Err_Password"] = true
 		ctx.Data["Err_RetypePasswd"] = true
-		ctx.RenderWithErr("Password and re-type password are not same.", "user/signup", &form)
+		ctx.RenderWithErr("Password and re-type password are not same.", SIGNUP, &form)
 		return
 	}
 
@@ -217,21 +226,23 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
 	}
 
 	var err error
-	if u, err = models.RegisterUser(u); err != nil {
+	if u, err = models.CreateUser(u); err != nil {
 		switch err {
 		case models.ErrUserAlreadyExist:
-			ctx.RenderWithErr("Username has been already taken", "user/signup", &form)
+			ctx.Data["Err_UserName"] = true
+			ctx.RenderWithErr("Username has been already taken", SIGNUP, &form)
 		case models.ErrEmailAlreadyUsed:
-			ctx.RenderWithErr("E-mail address has been already used", "user/signup", &form)
+			ctx.Data["Err_Email"] = true
+			ctx.RenderWithErr("E-mail address has been already used", SIGNUP, &form)
 		case models.ErrUserNameIllegal:
-			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "user/signup", &form)
+			ctx.Data["Err_UserName"] = true
+			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), SIGNUP, &form)
 		default:
-			ctx.Handle(500, "user.SignUp(RegisterUser)", err)
+			ctx.Handle(500, "user.SignUpPost(CreateUser)", err)
 		}
 		return
 	}
-
-	log.Trace("%s User created: %s", ctx.Req.RequestURI, form.UserName)
+	log.Trace("%s User created: %s", ctx.Req.RequestURI, u.Name)
 
 	// Bind social account.
 	if isOauth {
@@ -256,6 +267,7 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
 		}
 		return
 	}
+
 	ctx.Redirect("/user/login")
 }
 
@@ -263,7 +275,7 @@ func Delete(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Delete Account"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingDelete"] = true
-	ctx.HTML(200, "user/delete")
+	ctx.HTML(200, DELETE)
 }
 
 func DeletePost(ctx *middleware.Context) {
@@ -284,7 +296,7 @@ func DeletePost(ctx *middleware.Context) {
 			case models.ErrUserOwnRepos:
 				ctx.Flash.Error("Your account still have ownership of repository, you have to delete or transfer them first.")
 			default:
-				ctx.Handle(500, "user.Delete", err)
+				ctx.Handle(500, "user.DeletePost(DeleteUser)", err)
 				return
 			}
 		} else {
@@ -319,7 +331,7 @@ func Activate(ctx *middleware.Context) {
 		} else {
 			ctx.Data["ServiceNotEnabled"] = true
 		}
-		ctx.HTML(200, "user/activate")
+		ctx.HTML(200, ACTIVATE)
 		return
 	}
 
@@ -341,7 +353,7 @@ func Activate(ctx *middleware.Context) {
 	}
 
 	ctx.Data["IsActivateFailed"] = true
-	ctx.HTML(200, "user/activate")
+	ctx.HTML(200, ACTIVATE)
 }
 
 func ForgotPasswd(ctx *middleware.Context) {
@@ -349,12 +361,12 @@ func ForgotPasswd(ctx *middleware.Context) {
 
 	if setting.MailService == nil {
 		ctx.Data["IsResetDisable"] = true
-		ctx.HTML(200, "user/forgot_passwd")
+		ctx.HTML(200, FORGOT_PASSWORD)
 		return
 	}
 
 	ctx.Data["IsResetRequest"] = true
-	ctx.HTML(200, "user/forgot_passwd")
+	ctx.HTML(200, FORGOT_PASSWORD)
 }
 
 func ForgotPasswdPost(ctx *middleware.Context) {
@@ -379,7 +391,7 @@ func ForgotPasswdPost(ctx *middleware.Context) {
 
 	if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
 		ctx.Data["ResendLimited"] = true
-		ctx.HTML(200, "user/forgot_passwd")
+		ctx.HTML(200, FORGOT_PASSWORD)
 		return
 	}
 
@@ -391,7 +403,7 @@ func ForgotPasswdPost(ctx *middleware.Context) {
 	ctx.Data["Email"] = email
 	ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
 	ctx.Data["IsResetSent"] = true
-	ctx.HTML(200, "user/forgot_passwd")
+	ctx.HTML(200, FORGOT_PASSWORD)
 }
 
 func ResetPasswd(ctx *middleware.Context) {
@@ -404,7 +416,7 @@ func ResetPasswd(ctx *middleware.Context) {
 	}
 	ctx.Data["Code"] = code
 	ctx.Data["IsResetForm"] = true
-	ctx.HTML(200, "user/reset_passwd")
+	ctx.HTML(200, RESET_PASSWORD)
 }
 
 func ResetPasswdPost(ctx *middleware.Context) {
@@ -441,5 +453,5 @@ func ResetPasswdPost(ctx *middleware.Context) {
 	}
 
 	ctx.Data["IsResetFailed"] = true
-	ctx.HTML(200, "user/reset_passwd")
+	ctx.HTML(200, RESET_PASSWORD)
 }

+ 1 - 1
templates/VERSION

@@ -1 +1 @@
-0.4.1.0601 Alpha
+0.4.5.0628 Alpha

+ 3 - 3
templates/admin/auths/edit.tmpl → templates/admin/auth/edit.tmpl

@@ -71,21 +71,21 @@
                     <div class="form-group {{if .Err_Attributes}}has-error has-feedback{{end}}">
                         <label class="col-md-3 control-label">Search Attributes: </label>
                         <div class="col-md-7">
-                            <input name="attributes" class="form-control" placeholder="Type search attributes" value="{{.Source.LDAP.Attributes}}" required="required">
+                            <input name="attributes" class="form-control" placeholder="Type search attributes" value="{{.Source.LDAP.Attributes}}">
                         </div>
                     </div>
 
                     <div class="form-group {{if .Err_Filter}}has-error has-feedback{{end}}">
                         <label class="col-md-3 control-label">Search Filter: </label>
                         <div class="col-md-7">
-                            <input name="filter" class="form-control" placeholder="Type search filter" value="{{.Source.LDAP.Filter}}" required="required">
+                            <input name="filter" class="form-control" placeholder="Type search filter" value="{{.Source.LDAP.Filter}}">
                         </div>
                     </div>
 
                     <div class="form-group {{if .Err_MsAdSA}}has-error has-feedback{{end}}">
                         <label class="col-md-3 control-label">Ms Ad SA: </label>
                         <div class="col-md-7">
-                            <input name="ms_ad_sa" class="form-control" placeholder="Type Ms Ad SA" value="{{.Source.LDAP.MsAdSAFormat}}" required="required">
+                            <input name="ms_ad_sa" class="form-control" placeholder="Type Ms Ad SA" value="{{.Source.LDAP.MsAdSAFormat}}">
                         </div>
                     </div>
                     {{else if eq $type 3}}

+ 0 - 0
templates/admin/auths/new.tmpl → templates/admin/auth/new.tmpl


+ 18 - 1
templates/admin/config.tmpl

@@ -36,6 +36,8 @@
                     <dd>{{.LogRootPath}}</dd>
                     <dt>Script Type</dt>
                     <dd>{{.ScriptType}}</dd>
+                    <dt>Reverse Authentication User</dt>
+                    <dd>{{.ReverseProxyAuthUser}}</dd>
                 </dl>
             </div>
         </div>
@@ -77,7 +79,7 @@
                     <dt>Require Sign In View</dt>
                     <dd><i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></dd>
                     <dt>Mail Notification</dt>
-                    <dd><i class="fa fa{{if .Service.NotifyMail}}-check{{end}}-square-o"></i></dd>
+                    <dd><i class="fa fa{{if .Service.EnableNotifyMail}}-check{{end}}-square-o"></i></dd>
                     <dt>Enable Cache Avatar</dt>
                     <dd><i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></dd>
                     <hr/>
@@ -89,6 +91,21 @@
             </div>
         </div>
 
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                Webhook Configuration
+            </div>
+
+            <div class="panel-body">
+                <dl class="dl-horizontal admin-dl-horizontal">
+                    <dt>Task Interval</dt>
+                    <dd>{{.WebhookTaskInterval}} minutes</dd>
+                    <dt>Deliver Timeout</dt>
+                    <dd>{{.WebhookDeliverTimeout}} seconds</dd>
+                </dl>
+            </div>
+        </div>
+
         <div class="panel panel-default">
             <div class="panel-heading">
                 Mailer Configuration

+ 4 - 0
templates/admin/dashboard.tmpl

@@ -32,6 +32,10 @@
                             <td>Clean unbind OAuthes</td>
                             <td><i class="fa fa-caret-square-o-right"></i> <a href="/admin?op=1">Run</a></td>
                         </tr>
+                        <tr>
+                            <td>Delete inactivate accounts</td>
+                            <td><i class="fa fa-caret-square-o-right"></i> <a href="/admin?op=2">Run</a></td>
+                        </tr>
                     </tbody>
                 </table>
             </div>

+ 40 - 0
templates/admin/monitor/cron.tmpl

@@ -0,0 +1,40 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body" class="container" data-page="admin">
+    {{template "admin/nav" .}}
+    <div id="admin-container" class="col-md-10">
+        <ul class="nav nav-tabs">
+            <li{{if .PageIsMonitorCron}} class="active"{{end}}><a href="/admin/monitor">Cron Tasks</a></li>
+            <li{{if .PageIsMonitorProcess}} class="active"{{end}}><a href="/admin/monitor?tab=process">Processes</a></li>
+        </ul>
+        <div class="panel panel-default">
+            <div class="panel-body">
+                {{if .PageIsMonitorCron}}
+                <table class="table table-striped">
+                    <thead>
+                        <tr>
+                            <th>Name</th>
+                            <th>Schedule</th>
+                            <th>Next Time</th>
+                            <th>Previous Time</th>
+                            <th>Execute Times</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {{range .Entries}}
+                        <tr>
+                            <td>{{.Description}}</td>
+                            <td>{{.Spec}}</td>
+                            <td>{{.Next}}</td>
+                            <td>{{.Prev}}</td>
+                            <td>{{.ExecTimes}}</td>
+                        </tr>
+                        {{end}}
+                    </tbody>
+                </table>
+                {{end}}
+            </div>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 38 - 0
templates/admin/monitor/process.tmpl

@@ -0,0 +1,38 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body" class="container" data-page="admin">
+    {{template "admin/nav" .}}
+    <div id="admin-container" class="col-md-10">
+        <ul class="nav nav-tabs">
+            <li{{if .PageIsMonitorCron}} class="active"{{end}}><a href="/admin/monitor">Cron Tasks</a></li>
+            <li{{if .PageIsMonitorProcess}} class="active"{{end}}><a href="/admin/monitor?tab=process">Processes</a></li>
+        </ul>
+        <div class="panel panel-default">
+            <div class="panel-body">
+                {{if .PageIsMonitorProcess}}
+                <table class="table table-striped">
+                    <thead>
+                        <tr>
+                            <th>Pid</th>
+                            <th>Description</th>
+                            <th>Start Time</th>
+                            <th>Execution Time</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {{range .Processes}}
+                        <tr>
+                            <td>{{.Pid}}</td>
+                            <td>{{.Description}}</td>
+                            <td>{{.Start}}</td>
+                            <td>{{TimeSince .Start}}</td>
+                        </tr>
+                        {{end}}
+                    </tbody>
+                </table>
+                {{end}}
+            </div>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 1 - 0
templates/admin/nav.tmpl

@@ -5,5 +5,6 @@
         <li class="list-group-item{{if .PageIsRepos}} active{{end}}"><a href="/admin/repos"><i class="fa fa-book fa-lg"></i> Repositories</a></li>
         <li class="list-group-item{{if .PageIsAuths}} active{{end}}"><a href="/admin/auths"><i class="fa fa-certificate fa-lg"></i> Authentication</a></li>
         <li class="list-group-item{{if .PageIsConfig}} active{{end}}"><a href="/admin/config"><i class="fa fa-cogs fa-lg"></i> Configuration</a></li>
+        <li class="list-group-item{{if .PageIsMonitor}} active{{end}}"><a href="/admin/monitor"><i class="fa fa-th fa-lg"></i> Monitoring</a></li>
     </ul>
 </div>

+ 0 - 0
templates/admin/users/edit.tmpl → templates/admin/user/edit.tmpl


+ 0 - 0
templates/admin/users/new.tmpl → templates/admin/user/new.tmpl


+ 1 - 1
templates/base/head.tmpl

@@ -14,7 +14,7 @@
 		 <!-- Stylesheets -->
 		{{if CdnMode}}
 		<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
-		<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
+		<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
 
 		<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
 		<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>

+ 0 - 0
templates/mail/auth/active_email.tmpl → templates/mail/auth/active.tmpl


+ 75 - 0
templates/org/edit_team.tmpl

@@ -0,0 +1,75 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body-nav" class="org-nav org-nav-auto">
+    <div class="container clearfix">
+        <div id="org-nav-wrapper">
+            <ul class="nav nav-pills pull-right">
+                <li><a href="#"><i class="fa fa-users"></i>Members
+                    <span class="label label-default">5</span></a>
+                </li>
+                <li class="active"><a href="#"><i class="fa fa-tags"></i>Teams
+                    <span class="label label-default">2</span></a>
+                </li>
+            </ul>
+            <img class="pull-left org-small-logo" src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt="" width="60"/>
+            <div id="org-nav-info">
+                <h2 class="org-name">Organization Name</h2>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="body" class="container">
+    <div id="org">
+        <form id="org-teams-edit" class="form-horizontal card">
+            <h3>Edit team</h3>
+            <div class="form-group">
+                <label class="col-md-2 control-label">Team Name<strong class="text-danger">*</strong></label>
+                <div class="col-md-8">
+                    <input name="team" type="text" class="form-control" placeholder="Type your team name" value="" required="required">
+                    <span class="help-block">You'll use this name to mention this team in conversations.</span>
+                </div>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label">Description</label>
+                <div class="col-md-8">
+                    <input name="desc" type="text" class="form-control" placeholder="Type your team description (optional)" value="">
+                </div>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label">Permission</label>
+                <div class="col-md-8">
+                    <div class="radio">
+                        <label>
+                            <input type="radio" name="permission" value="pull" checked="">
+                            <strong>Read & Clone</strong>
+                        </label>
+                        <p>This team will be able to view and clone its repositories.</p>
+                    </div>
+                    <div class="radio">
+                        <label>
+                            <input type="radio" name="permission" value="push">
+                            <strong>Push, Read & Clone</strong>
+                        </label>
+                        <p>This team will be able to read its repositories, as well as push to them.</p>
+                    </div>
+                    <div class="radio">
+                        <label>
+                            <input type="radio" name="permission" value="admin">
+                            <strong>Collaboration, Push, Read & Clone</strong>
+                        </label>
+                        <p>This team will be able to push/pull to its repositories, as well as add other collaborators to them.</p>
+                    </div>
+                </div>
+            </div>
+            <hr/>
+            <div class="form-group">
+                <label class="col-md-2">&nbsp;</label>
+                <div class="col-md-8">
+                    <button class="btn btn-primary">Edit this team</button>
+                    <button class="btn btn-danger pull-right" value="delete" name="delete">Delete this team</button>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 56 - 0
templates/org/members.tmpl

@@ -0,0 +1,56 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body-nav" class="org-nav org-nav-auto">
+    <div class="container clearfix">
+        <div id="org-nav-wrapper">
+            <ul class="nav nav-pills pull-right">
+                <li class="active"><a href="#"><i class="fa fa-users"></i>Members
+                    <span class="label label-default">5</span></a>
+                </li>
+                <li><a href="#"><i class="fa fa-tags"></i>Teams
+                    <span class="label label-default">2</span></a>
+                </li>
+            </ul>
+            <img class="pull-left org-small-logo" src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt="" width="60"/>
+            <div id="org-nav-info">
+                <h2 class="org-name">Organization Name</h2>
+            </div>
+        </div>
+
+    </div>
+</div>
+<div id="body" class="container">
+    <div id="org">
+        <div id="org-members">
+            <div class="member">&nbsp;
+                <div class="avatar col-md-1">
+                    <img src="https://avatars3.githubusercontent.com/u/2142787?s=140" alt=""/>
+                </div>
+                <div class="name col-md-4">
+                    <a href="#"><strong>fuxiaohei</strong><span class="nick">傅小黑</span></a>
+                </div>
+                <div class="role col-md-2 pull-right">
+                    <strong>Member</strong>
+                </div>
+                <div class="status col-md-1 pull-right">
+                    <strong>Public</strong>
+                </div>
+            </div>
+            <div class="member">&nbsp;
+                <div class="avatar col-md-1">
+                    <img src="https://avatars3.githubusercontent.com/u/2142787?s=140" alt=""/>
+                </div>
+                <div class="name col-md-4">
+                    <a href="#"><strong>fuxiaohei</strong><span class="nick">傅小黑</span></a>
+                </div>
+                <div class="role col-md-2 pull-right">
+                    <strong><i class="fa fa-user"></i>Owner</strong>
+                </div>
+                <div class="status col-md-1 pull-right">
+                    <i class="fa fa-lock"></i>Private
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 32 - 0
templates/org/new.tmpl

@@ -0,0 +1,32 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div class="container" id="body">
+    <form action="/org/create" method="post" class="form-horizontal card" id="org-create">
+        {{.CsrfTokenHtml}}
+        <h3>Create New Organization</h3>
+        {{template "base/alert" .}}
+        <div class="form-group {{if .Err_OrgName}}has-error has-feedback{{end}}">
+            <label class="col-md-2 control-label">Organization<strong class="text-danger">*</strong></label>
+            <div class="col-md-8">
+                <input name="orgname" type="text" class="form-control" placeholder="Type your organization name" value="{{.orgname}}" required="required">
+                <span class="help-block">Great organization names are short and memorable. </span>
+            </div>
+        </div>
+
+        <div class="form-group {{if .Err_Email}}has-error has-feedback{{end}}">
+            <label class="col-md-2 control-label">Email<strong class="text-danger">*</strong></label>
+            <div class="col-md-8">
+                <input name="email" type="text" class="form-control" placeholder="Type organization's email" value="{{.email}}" required="required">
+                <span class="help-block">Organization's Email receives all notifications and confirmations.</span>
+            </div>
+        </div>
+
+        <div class="form-group">
+            <div class="col-md-offset-2 col-md-8">
+                <button type="submit" class="btn btn-lg btn-primary">Create An Organization</button>
+                <a href="/" class="text-danger">Cancel</a>
+            </div>
+        </div>
+    </form>
+</div>
+{{template "base/footer" .}}

+ 74 - 0
templates/org/new_team.tmpl

@@ -0,0 +1,74 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body-nav" class="org-nav org-nav-auto">
+    <div class="container clearfix">
+        <div id="org-nav-wrapper">
+            <ul class="nav nav-pills pull-right">
+                <li><a href="#"><i class="fa fa-users"></i>Members
+                    <span class="label label-default">5</span></a>
+                </li>
+                <li class="active"><a href="#"><i class="fa fa-tags"></i>Teams
+                    <span class="label label-default">2</span></a>
+                </li>
+            </ul>
+            <img class="pull-left org-small-logo" src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt="" width="60"/>
+            <div id="org-nav-info">
+                <h2 class="org-name">Organization Name</h2>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="body" class="container">
+    <div id="org">
+        <form id="org-teams-create" class="form-horizontal card">
+            <h3>Create new team</h3>
+            <div class="form-group">
+                <label class="col-md-2 control-label">Team Name<strong class="text-danger">*</strong></label>
+                <div class="col-md-8">
+                    <input name="team" type="text" class="form-control" placeholder="Type your team name" value="" required="required">
+                    <span class="help-block">You'll use this name to mention this team in conversations.</span>
+                </div>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label">Description</label>
+                <div class="col-md-8">
+                    <input name="desc" type="text" class="form-control" placeholder="Type your team description (optional)" value="">
+                </div>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label">Permission</label>
+                <div class="col-md-8">
+                    <div class="radio">
+                        <label>
+                            <input type="radio" name="permission" value="pull" checked="">
+                            <strong>Read & Clone</strong>
+                        </label>
+                        <p>This team will be able to view and clone its repositories.</p>
+                    </div>
+                    <div class="radio">
+                        <label>
+                            <input type="radio" name="permission" value="push">
+                            <strong>Push, Read & Clone</strong>
+                        </label>
+                        <p>This team will be able to read its repositories, as well as push to them.</p>
+                    </div>
+                    <div class="radio">
+                        <label>
+                            <input type="radio" name="permission" value="admin">
+                            <strong>Collaboration, Push, Read & Clone</strong>
+                        </label>
+                        <p>This team will be able to push/pull to its repositories, as well as add other collaborators to them.</p>
+                    </div>
+                </div>
+            </div>
+            <hr/>
+            <div class="form-group">
+                <label class="col-md-2">&nbsp;</label>
+                <div class="col-md-8">
+                    <button class="btn btn-primary">Create team</button>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 85 - 0
templates/org/org.tmpl

@@ -0,0 +1,85 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body-nav" class="org-nav">
+    <div class="container clearfix">
+        <div class="col-md-8" id="org-nav-wrapper">
+            <img class="pull-left org-logo" src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt="" width="100"/>
+            <div id="org-nav-info">
+                <h2 class="org-name">Organization Name</h2>
+                <p class="org-description">Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.</p>
+                <ul class="org-meta list-inline">
+                    <li><i class="fa fa-link"></i><a href="#">http://gogs.io</a></li>
+                    <li><i class="fa fa-envelope"></i><a href="#">info@gogs.io</a></li>
+                </ul>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="body" class="container">
+    <div id="org">
+        <div class="org-main col-md-8">
+            <div class="org-toolbar clearfix">
+                <button class="btn pull-right btn-success"><i class="fa fa-plus"></i> New Repository</button>
+            </div>
+            <hr style="width: 100%;border-color: #DDD"/>
+            <div class="org-repo-list" id="org-repo-list">
+                <div class="org-repo-item">
+                    <div class="org-repo-status pull-right">
+                        <ul class="list-inline">
+                            <li><strong>Go</strong></li>
+                            <li><i class="i fa fa-star"></i><strong>6</strong></li>
+                            <li><i class="fa fa-code-fork"></i><strong>2</strong></li>
+                        </ul>
+                    </div>
+                    <h3 class="org-repo-name"><a href="#">gogs</a></h3>
+                    <p class="org-repo-description">Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.</p>
+                    <p class="org-repo-update">Updated 17 hours ago</p>
+                </div>
+                <div class="org-repo-item">
+                    <div class="org-repo-status pull-right">
+                        <ul class="list-inline">
+                            <li><strong>Go</strong></li>
+                            <li><i class="i fa fa-star"></i><strong>6</strong></li>
+                            <li><i class="fa fa-code-fork"></i><strong>2</strong></li>
+                        </ul>
+                    </div>
+                    <h3 class="org-repo-name"><a href="#">gogs</a></h3>
+                    <p class="org-repo-description">Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.</p>
+                    <p class="org-repo-update">Updated 17 hours ago</p>
+                </div>
+            </div>
+        </div>
+        <div class="org-sidebar col-md-4">
+            <div class="org-panel panel panel-default" id="org-sidebar-members">
+                <div class="panel-heading"><strong>Members</strong></div>
+                <div class="panel-body">
+                    <a class="org-member" href="#" data-toggle="tooltip" title="username" data-placement="bottom"><img src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt=""/></a>
+                    <a class="org-member" href="#" data-toggle="tooltip" title="username" data-placement="bottom"><img src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt=""/></a>
+                    <a class="org-member" href="#" data-toggle="tooltip" title="username" data-placement="bottom"><img src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt=""/></a>
+                </div>
+            </div>
+            <div class="org-panel panel panel-default" id="org-sidebar-teams">
+                <div class="panel-heading"><strong>Teams</strong></div>
+                <div class="panel-body">
+                    <div class="org-team">
+                        <a href="#">
+                            <p class="org-team-name"><strong>Team name</strong></p>
+                            <p class="org-team-meta">
+                                4 members · 10 repositories
+                            </p>
+                        </a>
+                    </div>
+                    <div class="org-team">
+                        <a href="#">
+                            <p class="org-team-name"><strong>Team name</strong></p>
+                            <p class="org-team-meta">
+                                4 members · 10 repositories
+                            </p>
+                        </a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 130 - 0
templates/org/settings.tmpl

@@ -0,0 +1,130 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body-nav">
+    <div class="container">
+        <div class="btn-group pull-left" id="dashboard-switch">
+            <button type="button" class="btn btn-default">
+                <img src="{{.Org.AvatarLink}}?s=28" alt="user-avatar" title="username">
+                {{.Org.Name}}
+            </button>
+        </div>
+        <ul class="nav nav-pills pull-right">
+            <li><a href="/org/{{.Org.Name}}/dashboard/">News Feed</a></li>
+            <li><a href="/org/{{.Org.Name}}/dashboard/issues">Issues</a></li>
+            <li class="active"><a href="/org/{{.Org.Name}}/settings">Settings</a></li>
+            <!-- <li><a href="/pulls">Pull Requests</a></li>
+            <li><a href="/stars">Stars</a></li> -->
+        </ul>
+    </div>
+</div>
+
+<div id="body" class="container" data-page="org">
+    <div id="user-setting-nav" class="col-md-2 repo-setting-nav">
+        <ul class="list-group">
+            <li class="list-group-item active"><a href="#">Options</a></li>
+        </ul>
+    </div>
+    <div id="repo-setting-container" class="col-md-10">
+        {{template "base/alert" .}}
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                Organization Options
+            </div>
+
+            <div class="panel-body">
+                <form action="/org/{{.Org.Name}}/settings" method="post" class="form-horizontal">
+                    {{.CsrfTokenHtml}}
+                    <input type="hidden" name="action" value="update">
+
+                    <div class="form-group{{if .Err_DisplayName}} has-error has-feedback{{end}}">
+                        <label class="col-md-3 text-right" for="org-setting-name">Display Name</label>
+                        <div class="col-md-9">
+                            <input class="form-control" name="display_name" value="{{.Org.FullName}}" title="" id="org-setting-name"/>
+                        </div>
+                    </div>
+
+                    <div class="form-group{{if .Err_Email}} has-error has-feedback{{end}}">
+                        <label class="col-md-3 text-right" for="org-email">Email</label>
+                        <div class="col-md-9">
+                            <input class="form-control" name="email" value="{{.Org.Email}}" title="" id="org-email" type="email"/>
+                        </div>
+                    </div>
+
+                    <div class="form-group{{if .Err_Description}} has-error has-feedback{{end}}">
+                        <label class="col-md-3 text-right" for="org-desc">Description</label>
+                        <div class="col-md-9">
+                            <textarea class="form-control" name="desc" id="org-desc" rows="3">{{.Org.Description}}</textarea>
+                        </div>
+                    </div>
+
+                    <div class="form-group{{if .Err_Website}} has-error has-feedback{{end}}">
+                        <label class="col-md-3 text-right" for="org-site">Official Site</label>
+                        <div class="col-md-9">
+                            <input type="url" class="form-control" name="site" value="{{.Org.Website}}" id="org-site"/>
+                        </div>
+                    </div>
+
+                    <div class="form-group{{if .Err_Location}} has-error has-feedback{{end}}">
+                        <label class="col-md-3 text-right" for="org-location">Location</label>
+                        <div class="col-md-9">
+                            <input class="form-control" name="location" value="{{.Org.Location}}" title="" id="org-location"/>
+                        </div>
+                    </div>
+
+                    <div class="form-group">
+                        <div class="col-md-9 col-md-offset-3">
+                            <button class="btn btn-primary" type="submit">Save Options</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+        </div>
+
+        <div class="panel panel-warning">
+            <div class="panel-heading">
+                Danger Zone
+            </div>
+            <div class="panel-body">
+                <button type="button" class="btn btn-default pull-right" href="#delete-org-modal" data-toggle="modal">
+                    Delete this organization
+                </button>
+                <dd>
+                <dt>Delete this organization</dt>
+                <dl>Once you delete this organization and all repositories in, there is no going back. Please be
+                    certain.
+                </dl>
+                </dd>
+
+                <div class="modal fade" id="delete-org-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
+                     aria-hidden="true">
+                    <div class="modal-dialog">
+                        <form action="/org/{{.Org.Name}}/settings/delete" method="post"
+                              class="modal-content">
+                            {{.CsrfTokenHtml}}
+                            <div class="modal-header">
+                                <button type="button" class="close" data-dismiss="modal"
+                                        aria-hidden="true">&times;</button>
+                                <h4 class="modal-title" id="myModalLabel">Delete organization</h4>
+                            </div>
+
+                            <div class="modal-body">
+                                <div class="form-group">
+                                    <label>Make sure your are owner of this organization. Please enter your password.<strong class="text-danger">*</strong></label>
+                                    <input name="password" class="form-control" type="password" placeholder="Type your account password" required="required">
+                                </div>
+                            </div>
+
+                            <div class="modal-footer">
+                                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+                                <button class="btn btn-danger btn-lg">I understand the consequences, delete this
+                                    organization
+                                </button>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 71 - 0
templates/org/teams.tmpl

@@ -0,0 +1,71 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="body-nav" class="org-nav org-nav-auto">
+    <div class="container clearfix">
+        <div id="org-nav-wrapper">
+            <ul class="nav nav-pills pull-right">
+                <li><a href="#"><i class="fa fa-users"></i>Members
+                    <span class="label label-default">5</span></a>
+                </li>
+                <li class="active"><a href="#"><i class="fa fa-tags"></i>Teams
+                    <span class="label label-default">2</span></a>
+                </li>
+            </ul>
+            <img class="pull-left org-small-logo" src="https://avatars3.githubusercontent.com/u/6656686?s=140" alt="" width="60"/>
+            <div id="org-nav-info">
+                <h2 class="org-name">Organization Name</h2>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="body" class="container">
+    <div id="org">
+        <div id="org-teams">
+            <div id="org-teams-action">
+                <div class="col-md-12">
+                    <a href="#"><button class="btn btn-success"><i class="fa fa-plus-square"></i>New Team</button></a>
+                    <hr/>
+                </div>
+            </div>
+            <div class="org-team col-md-6">
+                <div class="panel panel-default">
+                    <h2 class="panel-heading org-team-name"><a href="#"><strong>Team Name</strong></a></h2>
+                    <div class="panel-body">
+                        <p class="org-team-meta">4 members · 10 repositories</p>
+                        <p class="org-team-members">
+                            <a href="#">
+                                <img class="img-thumbnail" src="https://avatars2.githubusercontent.com/u/2946214?s=60" alt=""/>
+                            </a>
+                            <a href="#">
+                                <img class="img-thumbnail" src="https://avatars2.githubusercontent.com/u/2946214?s=60" alt=""/>
+                            </a>
+                        </p>
+                    </div>
+                    <div class="panel-footer">
+                        <button class="pull-right btn btn-default">Join</button>
+                    </div>
+                </div>
+            </div>
+            <div class="org-team col-md-6">
+                <div class="panel panel-default">
+                    <h2 class="panel-heading org-team-name"><a href="#"><strong>Team Name</strong></a></h2>
+                    <div class="panel-body">
+                        <p class="org-team-meta">4 members · 10 repositories</p>
+                        <p class="org-team-members">
+                            <a href="#">
+                                <img class="img-thumbnail" src="https://avatars2.githubusercontent.com/u/2946214?s=60" alt=""/>
+                            </a>
+                            <a href="#">
+                                <img class="img-thumbnail" src="https://avatars2.githubusercontent.com/u/2946214?s=60" alt=""/>
+                            </a>
+                        </p>
+                    </div>
+                    <div class="panel-footer">
+                        <button class="pull-right btn btn-danger">Leave</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 0 - 0
templates/repo/branches.tmpl → templates/repo/branch.tmpl


+ 30 - 2
templates/repo/create.tmpl

@@ -8,9 +8,37 @@
         <div class="form-group">
             <label class="col-md-2 control-label">Owner<strong class="text-danger">*</strong></label>
             <div class="col-md-8">
-                <p class="form-control-static">{{.SignedUserName}}</p>
-                <input type="hidden" value="{{.SignedUserId}}" name="userId"/>
+                <div class="btn-group" id="repo-owner-switch">
+                    <button type="button" class="btn btn-default" id="repo-owner-current">
+                        <img src="{{.SignedUser.AvatarLink}}?s=28" alt="user-avatar" title="username" id="repo-owner-avatar">
+                        <span id="repo-owner-name">{{.SignedUser.Name}}</span>
+                    </button>
+                    <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+                        <span class="caret"></span>
+                    </button>
+                    <div class="dropdown-menu clone-group-btn no-propagation">
+                        <ul id="dashboard-switch-menu" class="list-unstyled">
+                            <li data-uid="{{.SignedUser.Id}}" class="checked">
+                                <a>
+                                    <i class="fa fa-check"></i>
+                                    <img src="{{.SignedUser.AvatarLink}}?s=28" alt="user-avatar" title="username">
+                                    {{.SignedUser.Name}}
+                                </a>
+                            </li>
+                            {{range .Orgs}}
+                            <li data-uid="{{.Id}}">
+                                <a>
+                                    <i class="fa fa-check"></i>
+                                    <img src="{{.AvatarLink}}?s=28" alt="user-avatar" title="username">
+                                    {{.Name}}
+                                </a>
+                            </li>
+                            {{end}}
+                        </ul>
+                    </div>
+                </div>
             </div>
+            <input type="hidden" value="{{.SignedUserId}}" name="uid" id="repo-owner-id"/>
         </div>
 
         <div class="form-group {{if .Err_RepoName}}has-error has-feedback{{end}}">

+ 0 - 0
templates/repo/hooks_add.tmpl → templates/repo/hook_add.tmpl


+ 0 - 0
templates/repo/hooks_edit.tmpl → templates/repo/hook_edit.tmpl


+ 0 - 0
templates/issue/create.tmpl → templates/repo/issue/create.tmpl


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