Browse Source

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

Lunny Xiao 10 years ago
parent
commit
a357cda957
100 changed files with 5052 additions and 1605 deletions
  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/juju2013/goldap = `commit:f4a7f67`
 github.com/lib/pq = `commit:529edd9`
 github.com/lib/pq = `commit:529edd9`
 github.com/nfnt/resize = `commit:8aee0d9`
 github.com/nfnt/resize = `commit:8aee0d9`
-github.com/qiniu/log = `commit:891d1cb`
-github.com/robfig/cron = `commit:b024fc5`
 
 
 [res]
 [res]
 include = templates|public
 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)
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
 
-##### Current version: 0.4.0 Alpha
+##### Current version: 0.4.5 Alpha
 
 
 ### NOTICES
 ### 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.
 - Demo site [try.gogits.org](http://try.gogits.org) is running under `dev` branch.
 
 
 #### Other language version
 #### Other language version
@@ -33,7 +33,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
 
 
 - Activity timeline
 - Activity timeline
 - SSH/HTTP(S) protocol support
 - SSH/HTTP(S) protocol support
-- SMTP/LDAP authentication support
+- SMTP/LDAP/reverse proxy authentication support
 - Register/delete/rename account
 - Register/delete/rename account
 - Create/migrate/mirror/delete/watch/rename/transfer public/private repository
 - Create/migrate/mirror/delete/watch/rename/transfer public/private repository
 - Repository viewer/release/issue tracker/webhooks
 - 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.
 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
 ## 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.
 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)
 ![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) 协议
 - 支持 SSH/HTTP(S) 协议
-- 支持 SMTP/LDAP 用户认证
+- 支持 SMTP/LDAP/反向代理 用户认证
 - 注册/删除/重命名用户
 - 注册/删除/重命名用户
 - 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
 - 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
 - 仓库 浏览器/发布/缺陷管理/Web 钩子
 - 仓库 浏览器/发布/缺陷管理/Web 钩子
@@ -66,9 +66,6 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
 
 
 本项目的 [开发团队](http://gogs.io/team)。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
 本项目的 [开发团队](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) 文件中。
 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。

+ 142 - 12
cmd/fix.go

@@ -5,10 +5,16 @@
 package cmd
 package cmd
 
 
 import (
 import (
+	"bufio"
 	"fmt"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"os"
 	"os"
+	"path"
+	"strings"
 
 
 	"github.com/codegangsta/cli"
 	"github.com/codegangsta/cli"
+
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
@@ -16,28 +22,152 @@ import (
 var CmdFix = cli.Command{
 var CmdFix = cli.Command{
 	Name:        "fix",
 	Name:        "fix",
 	Usage:       "This command for upgrade from old version",
 	Usage:       "This command for upgrade from old version",
-	Description: `Fix provide upgrade from old version`,
 	Action:      runFix,
 	Action:      runFix,
+	Subcommands: fixCommands,
 	Flags:       []cli.Flag{},
 	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 {
 	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)
 		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"
 	"strings"
 
 
 	"github.com/codegangsta/cli"
 	"github.com/codegangsta/cli"
-	qlog "github.com/qiniu/log"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
@@ -27,27 +27,13 @@ var CmdServ = cli.Command{
 	Flags:       []cli.Flag{},
 	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) {
 func setup(logPath string) {
-	workDir, _ := setting.WorkDir()
-	newLogger(path.Join(workDir, logPath))
-
 	setting.NewConfigContext()
 	setting.NewConfigContext()
+	log.NewGitLogger(path.Join(setting.LogRootPath, logPath))
 	models.LoadModelsConfig()
 	models.LoadModelsConfig()
 
 
 	if models.UseSQLite3 {
 	if models.UseSQLite3 {
+		workDir, _ := setting.WorkDir()
 		os.Chdir(workDir)
 		os.Chdir(workDir)
 	}
 	}
 
 
@@ -70,45 +56,45 @@ func parseCmd(cmd string) (string, string) {
 }
 }
 
 
 var (
 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]
 	_, e := sl[b]
 	return e
 	return e
 }
 }
 
 
 func runServ(k *cli.Context) {
 func runServ(k *cli.Context) {
-	setup(path.Join(setting.LogRootPath, "serv.log"))
+	setup("serv.log")
 
 
 	keys := strings.Split(os.Args[2], "-")
 	keys := strings.Split(os.Args[2], "-")
 	if len(keys) != 2 {
 	if len(keys) != 2 {
 		println("Gogs: auth file format error")
 		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)
 	keyId, err := strconv.ParseInt(keys[1], 10, 64)
 	if err != nil {
 	if err != nil {
 		println("Gogs: auth file format error")
 		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)
 	user, err := models.GetUserByKeyId(keyId)
 	if err != nil {
 	if err != nil {
 		if err == models.ErrUserNotKeyOwner {
 		if err == models.ErrUserNotKeyOwner {
 			println("Gogs: you are not the owner of SSH key")
 			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)
 		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")
 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
@@ -122,7 +108,7 @@ func runServ(k *cli.Context) {
 	rr := strings.SplitN(repoPath, "/", 2)
 	rr := strings.SplitN(repoPath, "/", 2)
 	if len(rr) != 2 {
 	if len(rr) != 2 {
 		println("Gogs: unavailable repository", args)
 		println("Gogs: unavailable repository", args)
-		qlog.Fatalf("Unavailable repository: %v", args)
+		log.GitLogger.Fatal("Unavailable repository: %v", args)
 	}
 	}
 	repoUserName := rr[0]
 	repoUserName := rr[0]
 	repoName := strings.TrimSuffix(rr[1], ".git")
 	repoName := strings.TrimSuffix(rr[1], ".git")
@@ -134,45 +120,45 @@ func runServ(k *cli.Context) {
 	if err != nil {
 	if err != nil {
 		if err == models.ErrUserNotExist {
 		if err == models.ErrUserNotExist {
 			println("Gogs: given repository owner are not registered")
 			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)
 		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.
 	// Access check.
 	switch {
 	switch {
 	case isWrite:
 	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 {
 		if err != nil {
 			println("Gogs: internal error:", err)
 			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 {
 		} else if !has {
 			println("You have no right to write this repository")
 			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:
 	case isRead:
 		repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
 		repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
 		if err != nil {
 		if err != nil {
 			if err == models.ErrRepoNotExist {
 			if err == models.ErrRepoNotExist {
 				println("Gogs: given repository does not exist")
 				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)
 			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 {
 		if !repo.IsPrivate {
 			break
 			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 {
 		if err != nil {
 			println("Gogs: internal error:", err)
 			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 {
 		} else if !has {
 			println("You have no right to access this repository")
 			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:
 	default:
 		println("Unknown command")
 		println("Unknown command")
@@ -186,18 +172,9 @@ func runServ(k *cli.Context) {
 	gitcmd.Stdout = os.Stdout
 	gitcmd.Stdout = os.Stdout
 	gitcmd.Stdin = os.Stdin
 	gitcmd.Stdin = os.Stdin
 	gitcmd.Stderr = os.Stderr
 	gitcmd.Stderr = os.Stderr
-
-	if err = gitcmd.Run(); err != nil {
+	err = gitcmd.Run()
+	if err != nil {
 		println("Gogs: internal error:", err)
 		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 (
 import (
 	"os"
 	"os"
-	"path"
 	"strconv"
 	"strconv"
 
 
 	"github.com/codegangsta/cli"
 	"github.com/codegangsta/cli"
-	qlog "github.com/qiniu/log"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/modules/setting"
+	"github.com/gogits/gogs/modules/log"
 )
 )
 
 
 var CmdUpdate = cli.Command{
 var CmdUpdate = cli.Command{
@@ -24,35 +22,27 @@ var CmdUpdate = cli.Command{
 	Flags:       []cli.Flag{},
 	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) {
 func runUpdate(c *cli.Context) {
 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
 	if cmd == "" {
 	if cmd == "" {
 		return
 		return
 	}
 	}
 
 
-	setup(path.Join(setting.LogRootPath, "update.log"))
+	setup("update.log")
 
 
 	args := c.Args()
 	args := c.Args()
 	if len(args) != 3 {
 	if len(args) != 3 {
-		qlog.Fatal("received less 3 parameters")
+		log.GitLogger.Fatal("received less 3 parameters")
 	} else if args[0] == "" {
 	} 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")
 	userName := os.Getenv("userName")
 	userId, _ := strconv.ParseInt(os.Getenv("userId"), 10, 64)
 	userId, _ := strconv.ParseInt(os.Getenv("userId"), 10, 64)
-	//repoId := os.Getenv("repoId")
 	repoUserName := os.Getenv("repoUserName")
 	repoUserName := os.Getenv("repoUserName")
 	repoName := os.Getenv("repoName")
 	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/admin"
 	"github.com/gogits/gogs/routers/api/v1"
 	"github.com/gogits/gogs/routers/api/v1"
 	"github.com/gogits/gogs/routers/dev"
 	"github.com/gogits/gogs/routers/dev"
+	"github.com/gogits/gogs/routers/org"
 	"github.com/gogits/gogs/routers/repo"
 	"github.com/gogits/gogs/routers/repo"
 	"github.com/gogits/gogs/routers/user"
 	"github.com/gogits/gogs/routers/user"
 )
 )
@@ -89,11 +90,13 @@ func runWeb(*cli.Context) {
 	m.Get("/", ignSignIn, routers.Home)
 	m.Get("/", ignSignIn, routers.Home)
 	m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
 	m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
 	m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
 	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) {
 		m.Group("/v1", func(r martini.Router) {
 			// Miscellaneous.
 			// Miscellaneous.
 			r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
 			r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
@@ -159,8 +162,9 @@ func runWeb(*cli.Context) {
 	m.Group("/admin", func(r martini.Router) {
 	m.Group("/admin", func(r martini.Router) {
 		r.Get("/users", admin.Users)
 		r.Get("/users", admin.Users)
 		r.Get("/repos", admin.Repositories)
 		r.Get("/repos", admin.Repositories)
-		r.Get("/config", admin.Config)
 		r.Get("/auths", admin.Auths)
 		r.Get("/auths", admin.Auths)
+		r.Get("/config", admin.Config)
+		r.Get("/monitor", admin.Monitor)
 	}, adminReq)
 	}, adminReq)
 	m.Group("/admin/users", func(r martini.Router) {
 	m.Group("/admin/users", func(r martini.Router) {
 		r.Get("/new", admin.NewUser)
 		r.Get("/new", admin.NewUser)
@@ -184,6 +188,22 @@ func runWeb(*cli.Context) {
 
 
 	reqOwner := middleware.RequireOwner()
 	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) {
 	m.Group("/:username/:reponame", func(r martini.Router) {
 		r.Get("/settings", repo.Setting)
 		r.Get("/settings", repo.Setting)
 		r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
 		r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
@@ -221,11 +241,13 @@ func runWeb(*cli.Context) {
 		})
 		})
 
 
 		r.Post("/comment/:action", repo.Comment)
 		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))
 	}, reqSignIn, middleware.RepoAssignment(true))
 
 
 	m.Group("/:username/:reponame", func(r martini.Router) {
 	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))
 	}, reqSignIn, middleware.RepoAssignment(true, true))
 
 
 	m.Group("/:username/:reponame", func(r martini.Router) {
 	m.Group("/:username/:reponame", func(r martini.Router) {

+ 12 - 0
conf/app.ini

@@ -51,6 +51,8 @@ SECRET_KEY = !#@FDEWREWR&*(
 LOGIN_REMEMBER_DAYS = 7
 LOGIN_REMEMBER_DAYS = 7
 COOKIE_USERNAME = gogs_awesome
 COOKIE_USERNAME = gogs_awesome
 COOKIE_REMEMBER_NAME = gogs_incredible
 COOKIE_REMEMBER_NAME = gogs_incredible
+; Reverse proxy authentication header name of user name
+REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
 
 
 [service]
 [service]
 ACTIVE_CODE_LIVE_MINUTES = 180
 ACTIVE_CODE_LIVE_MINUTES = 180
@@ -65,6 +67,14 @@ REQUIRE_SIGNIN_VIEW = false
 ENABLE_CACHE_AVATAR = false
 ENABLE_CACHE_AVATAR = false
 ; Mail notification
 ; Mail notification
 ENABLE_NOTIFY_MAIL = false
 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]
 [mailer]
 ENABLED = false
 ENABLED = false
@@ -227,5 +237,7 @@ RECEIVERS =
 ; For "database" mode only
 ; For "database" mode only
 [log.database]
 [log.database]
 LEVEL = 
 LEVEL = 
+; Either "mysql" or "postgres"
 DRIVER = 
 DRIVER = 
+; Based on xorm, e.g.: root:root@localhost/gogs?charset=utf8
 CONN = 
 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, 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=""
 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.
 # Replace the database root password in database image Dockerfile.
 sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/$DB_TYPE/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. 
 # 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
   sed -i "${GOGS_BUILD_LINE}s/$/ -tags $MEM_TYPE/" images/gogits/Dockerfile
 
 
   cd images/$MEM_TYPE
   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 "
   MEM_LINK=" --link $MEM_RUN_NAME:mem "
   cd ../../
   cd ../../
 fi
 fi
 
 
 # Build the database image
 # Build the database image
 cd images/$DB_TYPE
 cd images/$DB_TYPE
-docker build -t gogits/$DB_TYPE .
+$DOCKER_BIN build -t gogits/$DB_TYPE .
 #
 #
 
 
 
 
 ## Build the gogits image
 ## Build the gogits image
 cd ../gogits
 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
 #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
 ## 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
 ## 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 "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"
 MYSQL_ALIAS="db"
 HOST_PORT="3000"
 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
 ## 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
 ## 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"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
-const APP_VER = "0.4.1.0601 Alpha"
+const APP_VER = "0.4.5.0628 Alpha"
 
 
 func init() {
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())
 	runtime.GOMAXPROCS(runtime.NumCPU())
@@ -31,10 +31,10 @@ func main() {
 	app.Version = APP_VER
 	app.Version = APP_VER
 	app.Commands = []cli.Command{
 	app.Commands = []cli.Command{
 		cmd.CmdWeb,
 		cmd.CmdWeb,
-		// cmd.CmdFix,
-		cmd.CmdDump,
 		cmd.CmdServ,
 		cmd.CmdServ,
 		cmd.CmdUpdate,
 		cmd.CmdUpdate,
+		cmd.CmdFix,
+		cmd.CmdDump,
 	}
 	}
 	app.Flags = append(app.Flags, []cli.Flag{}...)
 	app.Flags = append(app.Flags, []cli.Flag{}...)
 	app.Run(os.Args)
 	app.Run(os.Args)

+ 13 - 12
models/access.go

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

+ 22 - 17
models/action.go

@@ -12,10 +12,8 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gogits/git"
 	"github.com/gogits/git"
-	qlog "github.com/qiniu/log"
 
 
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/base"
-	"github.com/gogits/gogs/modules/hooks"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
 	"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())
 		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.
 	// New push event hook.
 	if err := repo.GetOwner(); err != nil {
 	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)
 	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 {
 	for i, cmt := range commit.Commits {
-		commits[i] = &hooks.PayloadCommit{
+		commits[i] = &PayloadCommit{
 			Id:      cmt.Sha1,
 			Id:      cmt.Sha1,
 			Message: cmt.Message,
 			Message: cmt.Message,
 			Url:     fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
 			Url:     fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
-			Author: &hooks.PayloadAuthor{
+			Author: &PayloadAuthor{
 				Name:  cmt.AuthorName,
 				Name:  cmt.AuthorName,
 				Email: cmt.AuthorEmail,
 				Email: cmt.AuthorEmail,
 			},
 			},
 		}
 		}
 	}
 	}
-	p := &hooks.Payload{
+	p := &Payload{
 		Ref:     refFullName,
 		Ref:     refFullName,
 		Commits: commits,
 		Commits: commits,
-		Repo: &hooks.PayloadRepo{
+		Repo: &PayloadRepo{
 			Id:          repo.Id,
 			Id:          repo.Id,
 			Name:        repo.LowerName,
 			Name:        repo.LowerName,
 			Url:         repoLink,
 			Url:         repoLink,
 			Description: repo.Description,
 			Description: repo.Description,
 			Website:     repo.Website,
 			Website:     repo.Website,
 			Watchers:    repo.NumWatches,
 			Watchers:    repo.NumWatches,
-			Owner: &hooks.PayloadAuthor{
+			Owner: &PayloadAuthor{
 				Name:  repoUserName,
 				Name:  repoUserName,
 				Email: actEmail,
 				Email: actEmail,
 			},
 			},
 			Private: repo.IsPrivate,
 			Private: repo.IsPrivate,
 		},
 		},
-		Pusher: &hooks.PayloadAuthor{
+		Pusher: &PayloadAuthor{
 			Name:  repo.Owner.LowerName,
 			Name:  repo.Owner.LowerName,
 			Email: repo.Owner.Email,
 			Email: repo.Owner.Email,
 		},
 		},
@@ -172,20 +170,27 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
 		}
 		}
 
 
 		p.Secret = w.Secret
 		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
 	return nil
 }
 }
 
 
 // NewRepoAction adds new action for creating repository.
 // 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
 		return err
 	}
 	}
 
 
-	log.Trace("action.NewRepoAction: %s/%s", user.LowerName, repo.LowerName)
+	log.Trace("action.NewRepoAction: %s/%s", u.LowerName, repo.LowerName)
 	return err
 	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.
 // GetFeeds returns action list of given user in given context.
 func GetFeeds(userid, offset int64, isProfile bool) ([]*Action, error) {
 func GetFeeds(userid, offset int64, isProfile bool) ([]*Action, error) {
 	actions := make([]*Action, 0, 20)
 	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 {
 	if isProfile {
 		sess.Where("is_private=?", false).And("act_user_id=?", userid)
 		sess.Where("is_private=?", false).And("act_user_id=?", userid)
 	} else {
 	} 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 (
 import (
 	"bufio"
 	"bufio"
+	"fmt"
 	"io"
 	"io"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
@@ -15,6 +16,7 @@ import (
 
 
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/process"
 )
 )
 
 
 // Diff line types.
 // Diff line types.
@@ -67,7 +69,7 @@ func (diff *Diff) NumFiles() int {
 
 
 const DIFF_HEAD = "diff --git "
 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)
 	scanner := bufio.NewScanner(reader)
 	var (
 	var (
 		curFile    *DiffFile
 		curFile    *DiffFile
@@ -169,11 +171,8 @@ func ParsePatch(cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
 	}
 	}
 
 
 	// In case process became zombie.
 	// 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
 	return diff, nil
 }
 }
@@ -207,5 +206,5 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
 		wr.Close()
 		wr.Close()
 	}()
 	}()
 	defer rd.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.
 // CreateIssue creates new issue for repository.
 func NewIssue(issue *Issue) (err error) {
 func NewIssue(issue *Issue) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -114,7 +114,7 @@ func NewIssue(issue *Issue) (err error) {
 // GetIssueByIndex returns issue by given index in repository.
 // GetIssueByIndex returns issue by given index in repository.
 func GetIssueByIndex(rid, index int64) (*Issue, error) {
 func GetIssueByIndex(rid, index int64) (*Issue, error) {
 	issue := &Issue{RepoId: rid, Index: index}
 	issue := &Issue{RepoId: rid, Index: index}
-	has, err := orm.Get(issue)
+	has, err := x.Get(issue)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -126,7 +126,7 @@ func GetIssueByIndex(rid, index int64) (*Issue, error) {
 // GetIssueById returns an issue by ID.
 // GetIssueById returns an issue by ID.
 func GetIssueById(id int64) (*Issue, error) {
 func GetIssueById(id int64) (*Issue, error) {
 	issue := &Issue{Id: id}
 	issue := &Issue{Id: id}
-	has, err := orm.Get(issue)
+	has, err := x.Get(issue)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -137,7 +137,7 @@ func GetIssueById(id int64) (*Issue, error) {
 
 
 // GetIssues returns a list of issues by given conditions.
 // 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) {
 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 {
 	if rid > 0 {
 		sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
 		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.
 // GetIssuesByLabel returns a list of issues by given label and repository.
 func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
 func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
 	issues := make([]*Issue, 0, 10)
 	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
 	return issues, err
 }
 }
 
 
 // GetIssueCountByPoster returns number of issues of repository by poster.
 // GetIssueCountByPoster returns number of issues of repository by poster.
 func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
 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
 	return count
 }
 }
 
 
@@ -213,9 +213,9 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
 // IssueUser represents an issue-user relation.
 // IssueUser represents an issue-user relation.
 type IssueUser struct {
 type IssueUser struct {
 	Id          int64
 	Id          int64
-	Uid         int64 // User ID.
+	Uid         int64 `xorm:"INDEX"` // User ID.
 	IssueId     int64
 	IssueId     int64
-	RepoId      int64
+	RepoId      int64 `xorm:"INDEX"`
 	MilestoneId int64
 	MilestoneId int64
 	IsRead      bool
 	IsRead      bool
 	IsAssigned  bool
 	IsAssigned  bool
@@ -241,7 +241,7 @@ func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err erro
 			isNeedAddPoster = false
 			isNeedAddPoster = false
 		}
 		}
 		iu.IsAssigned = iu.Uid == aid
 		iu.IsAssigned = iu.Uid == aid
-		if _, err = orm.Insert(iu); err != nil {
+		if _, err = x.Insert(iu); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -249,7 +249,7 @@ func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err erro
 		iu.Uid = pid
 		iu.Uid = pid
 		iu.IsPoster = true
 		iu.IsPoster = true
 		iu.IsAssigned = iu.Uid == aid
 		iu.IsAssigned = iu.Uid == aid
-		if _, err = orm.Insert(iu); err != nil {
+		if _, err = x.Insert(iu); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -270,7 +270,7 @@ func PairsContains(ius []*IssueUser, issueId int64) int {
 // GetIssueUserPairs returns issue-user pairs by given repository and user.
 // GetIssueUserPairs returns issue-user pairs by given repository and user.
 func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
 func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
 	ius := make([]*IssueUser, 0, 10)
 	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
 	return ius, err
 }
 }
 
 
@@ -285,7 +285,7 @@ func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*Issue
 	cond := strings.TrimSuffix(buf.String(), " OR ")
 	cond := strings.TrimSuffix(buf.String(), " OR ")
 
 
 	ius := make([]*IssueUser, 0, 10)
 	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 {
 	if len(cond) > 0 {
 		sess.And(cond)
 		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.
 // GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
 func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
 func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
 	ius := make([]*IssueUser, 0, 10)
 	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 {
 	if rid > 0 {
 		sess.And("repo_id=?", rid)
 		sess.And("repo_id=?", rid)
 	}
 	}
@@ -335,7 +335,7 @@ func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStat
 	issue := new(Issue)
 	issue := new(Issue)
 	tmpSess := &xorm.Session{}
 	tmpSess := &xorm.Session{}
 
 
-	sess := orm.Where("repo_id=?", rid)
+	sess := x.Where("repo_id=?", rid)
 	*tmpSess = *sess
 	*tmpSess = *sess
 	stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
 	stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
 	*tmpSess = *sess
 	*tmpSess = *sess
@@ -347,7 +347,7 @@ func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStat
 	}
 	}
 
 
 	if filterMode != FM_MENTION {
 	if filterMode != FM_MENTION {
-		sess = orm.Where("repo_id=?", rid)
+		sess = x.Where("repo_id=?", rid)
 		switch filterMode {
 		switch filterMode {
 		case FM_ASSIGN:
 		case FM_ASSIGN:
 			sess.And("assignee_id=?", uid)
 			sess.And("assignee_id=?", uid)
@@ -361,16 +361,16 @@ func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStat
 		*tmpSess = *sess
 		*tmpSess = *sess
 		stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
 		stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
 	} else {
 	} 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
 		*tmpSess = *sess
 		stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
 		stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
 		*tmpSess = *sess
 		*tmpSess = *sess
 		stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
 		stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
 	}
 	}
 nofilter:
 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
 	return stats
 }
 }
 
 
@@ -378,28 +378,28 @@ nofilter:
 func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
 func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
 	stats := &IssueStats{}
 	stats := &IssueStats{}
 	issue := new(Issue)
 	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
 	return stats
 }
 }
 
 
 // UpdateIssue updates information of issue.
 // UpdateIssue updates information of issue.
 func UpdateIssue(issue *Issue) error {
 func UpdateIssue(issue *Issue) error {
-	_, err := orm.Id(issue.Id).AllCols().Update(issue)
+	_, err := x.Id(issue.Id).AllCols().Update(issue)
 	return err
 	return err
 }
 }
 
 
 // UpdateIssueUserByStatus updates issue-user pairs by issue status.
 // UpdateIssueUserByStatus updates issue-user pairs by issue status.
 func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
 func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
 	rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
 	rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
-	_, err := orm.Exec(rawSql, isClosed, iid)
+	_, err := x.Exec(rawSql, isClosed, iid)
 	return err
 	return err
 }
 }
 
 
 // UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
 // UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
 func UpdateIssueUserPairByAssignee(aid, iid int64) error {
 func UpdateIssueUserPairByAssignee(aid, iid int64) error {
 	rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
 	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
 		return err
 	}
 	}
 
 
@@ -408,14 +408,14 @@ func UpdateIssueUserPairByAssignee(aid, iid int64) error {
 		return nil
 		return nil
 	}
 	}
 	rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
 	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
 	return err
 }
 }
 
 
 // UpdateIssueUserPairByRead updates issue-user pair for reading.
 // UpdateIssueUserPairByRead updates issue-user pair for reading.
 func UpdateIssueUserPairByRead(uid, iid int64) error {
 func UpdateIssueUserPairByRead(uid, iid int64) error {
 	rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
 	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
 	return err
 }
 }
 
 
@@ -423,16 +423,16 @@ func UpdateIssueUserPairByRead(uid, iid int64) error {
 func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
 func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
 	for _, uid := range uids {
 	for _, uid := range uids {
 		iu := &IssueUser{Uid: uid, IssueId: iid}
 		iu := &IssueUser{Uid: uid, IssueId: iid}
-		has, err := orm.Get(iu)
+		has, err := x.Get(iu)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 
 
 		iu.IsMentioned = true
 		iu.IsMentioned = true
 		if has {
 		if has {
-			_, err = orm.Id(iu.Id).AllCols().Update(iu)
+			_, err = x.Id(iu.Id).AllCols().Update(iu)
 		} else {
 		} else {
-			_, err = orm.Insert(iu)
+			_, err = x.Insert(iu)
 		}
 		}
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -467,7 +467,7 @@ func (m *Label) CalOpenIssues() {
 
 
 // NewLabel creates new label of repository.
 // NewLabel creates new label of repository.
 func NewLabel(l *Label) error {
 func NewLabel(l *Label) error {
-	_, err := orm.Insert(l)
+	_, err := x.Insert(l)
 	return err
 	return err
 }
 }
 
 
@@ -478,7 +478,7 @@ func GetLabelById(id int64) (*Label, error) {
 	}
 	}
 
 
 	l := &Label{Id: id}
 	l := &Label{Id: id}
-	has, err := orm.Get(l)
+	has, err := x.Get(l)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -490,13 +490,13 @@ func GetLabelById(id int64) (*Label, error) {
 // GetLabels returns a list of labels of given repository ID.
 // GetLabels returns a list of labels of given repository ID.
 func GetLabels(repoId int64) ([]*Label, error) {
 func GetLabels(repoId int64) ([]*Label, error) {
 	labels := make([]*Label, 0, 10)
 	labels := make([]*Label, 0, 10)
-	err := orm.Where("repo_id=?", repoId).Find(&labels)
+	err := x.Where("repo_id=?", repoId).Find(&labels)
 	return labels, err
 	return labels, err
 }
 }
 
 
 // UpdateLabel updates label information.
 // UpdateLabel updates label information.
 func UpdateLabel(l *Label) error {
 func UpdateLabel(l *Label) error {
-	_, err := orm.Id(l.Id).Update(l)
+	_, err := x.Id(l.Id).Update(l)
 	return err
 	return err
 }
 }
 
 
@@ -516,7 +516,7 @@ func DeleteLabel(repoId int64, strId string) error {
 		return err
 		return err
 	}
 	}
 
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -569,7 +569,7 @@ func (m *Milestone) CalOpenIssues() {
 
 
 // NewMilestone creates new milestone of repository.
 // NewMilestone creates new milestone of repository.
 func NewMilestone(m *Milestone) (err error) {
 func NewMilestone(m *Milestone) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -591,7 +591,7 @@ func NewMilestone(m *Milestone) (err error) {
 // GetMilestoneById returns the milestone by given ID.
 // GetMilestoneById returns the milestone by given ID.
 func GetMilestoneById(id int64) (*Milestone, error) {
 func GetMilestoneById(id int64) (*Milestone, error) {
 	m := &Milestone{Id: id}
 	m := &Milestone{Id: id}
-	has, err := orm.Get(m)
+	has, err := x.Get(m)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -603,7 +603,7 @@ func GetMilestoneById(id int64) (*Milestone, error) {
 // GetMilestoneByIndex returns the milestone of given repository and index.
 // GetMilestoneByIndex returns the milestone of given repository and index.
 func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
 func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
 	m := &Milestone{RepoId: repoId, Index: idx}
 	m := &Milestone{RepoId: repoId, Index: idx}
-	has, err := orm.Get(m)
+	has, err := x.Get(m)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} 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.
 // GetMilestones returns a list of milestones of given repository and status.
 func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
 func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
 	miles := make([]*Milestone, 0, 10)
 	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
 	return miles, err
 }
 }
 
 
 // UpdateMilestone updates information of given milestone.
 // UpdateMilestone updates information of given milestone.
 func UpdateMilestone(m *Milestone) error {
 func UpdateMilestone(m *Milestone) error {
-	_, err := orm.Id(m.Id).Update(m)
+	_, err := x.Id(m.Id).Update(m)
 	return err
 	return err
 }
 }
 
 
@@ -632,7 +632,7 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
 		return err
 		return err
 	}
 	}
 
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -658,7 +658,7 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
 
 
 // ChangeMilestoneAssign changes assignment of milestone for issue.
 // ChangeMilestoneAssign changes assignment of milestone for issue.
 func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
 func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -717,7 +717,7 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
 
 
 // DeleteMilestone deletes a milestone.
 // DeleteMilestone deletes a milestone.
 func DeleteMilestone(m *Milestone) (err error) {
 func DeleteMilestone(m *Milestone) (err error) {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -771,13 +771,13 @@ type Comment struct {
 	IssueId  int64
 	IssueId  int64
 	CommitId int64
 	CommitId int64
 	Line     int64
 	Line     int64
-	Content  string
+	Content  string    `xorm:"TEXT"`
 	Created  time.Time `xorm:"CREATED"`
 	Created  time.Time `xorm:"CREATED"`
 }
 }
 
 
 // CreateComment creates comment of issue or commit.
 // CreateComment creates comment of issue or commit.
 func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
 func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err := sess.Begin(); err != nil {
 	if err := sess.Begin(); err != nil {
 		return err
 		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.
 // GetIssueComments returns list of comment by given issue id.
 func GetIssueComments(issueId int64) ([]Comment, error) {
 func GetIssueComments(issueId int64) ([]Comment, error) {
 	comments := make([]Comment, 0, 10)
 	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
 	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
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
@@ -20,12 +20,13 @@ import (
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
 )
 )
 
 
-// Login types.
+type LoginType int
+
 const (
 const (
-	LT_NOTYPE = iota
-	LT_PLAIN
-	LT_LDAP
-	LT_SMTP
+	NOTYPE LoginType = iota
+	PLAIN
+	LDAP
+	SMTP
 )
 )
 
 
 var (
 var (
@@ -34,9 +35,9 @@ var (
 	ErrAuthenticationUserUsed     = errors.New("Authentication has been used by some users")
 	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.
 // Ensure structs implmented interface.
@@ -49,7 +50,6 @@ type LDAPConfig struct {
 	ldap.Ldapsource
 	ldap.Ldapsource
 }
 }
 
 
-// implement
 func (cfg *LDAPConfig) FromDB(bs []byte) error {
 func (cfg *LDAPConfig) FromDB(bs []byte) error {
 	return json.Unmarshal(bs, &cfg.Ldapsource)
 	return json.Unmarshal(bs, &cfg.Ldapsource)
 }
 }
@@ -65,7 +65,6 @@ type SMTPConfig struct {
 	TLS  bool
 	TLS  bool
 }
 }
 
 
-// implement
 func (cfg *SMTPConfig) FromDB(bs []byte) error {
 func (cfg *SMTPConfig) FromDB(bs []byte) error {
 	return json.Unmarshal(bs, cfg)
 	return json.Unmarshal(bs, cfg)
 }
 }
@@ -76,13 +75,13 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) {
 
 
 type LoginSource struct {
 type LoginSource struct {
 	Id                int64
 	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"`
 	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 {
 func (source *LoginSource) TypeString() string {
@@ -97,61 +96,59 @@ func (source *LoginSource) SMTP() *SMTPConfig {
 	return source.Cfg.(*SMTPConfig)
 	return source.Cfg.(*SMTPConfig)
 }
 }
 
 
-// for xorm callback
 func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
 func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
 	if colName == "type" {
 	if colName == "type" {
 		ty := (*val).(int64)
 		ty := (*val).(int64)
-		switch ty {
-		case LT_LDAP:
+		switch LoginType(ty) {
+		case LDAP:
 			source.Cfg = new(LDAPConfig)
 			source.Cfg = new(LDAPConfig)
-		case LT_SMTP:
+		case SMTP:
 			source.Cfg = new(SMTPConfig)
 			source.Cfg = new(SMTPConfig)
 		}
 		}
 	}
 	}
 }
 }
 
 
+func CreateSource(source *LoginSource) error {
+	_, err := x.Insert(source)
+	return err
+}
+
 func GetAuths() ([]*LoginSource, error) {
 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
 	return auths, err
 }
 }
 
 
 func GetLoginSourceById(id int64) (*LoginSource, error) {
 func GetLoginSourceById(id int64) (*LoginSource, error) {
 	source := new(LoginSource)
 	source := new(LoginSource)
-	has, err := orm.Id(id).Get(source)
+	has, err := x.Id(id).Get(source)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
-	}
-	if !has {
+	} else if !has {
 		return nil, ErrAuthenticationNotExist
 		return nil, ErrAuthenticationNotExist
 	}
 	}
 	return source, nil
 	return source, nil
 }
 }
 
 
-func AddSource(source *LoginSource) error {
-	_, err := orm.Insert(source)
-	return err
-}
-
 func UpdateSource(source *LoginSource) error {
 func UpdateSource(source *LoginSource) error {
-	_, err := orm.Id(source.Id).AllCols().Update(source)
+	_, err := x.Id(source.Id).AllCols().Update(source)
 	return err
 	return err
 }
 }
 
 
 func DelLoginSource(source *LoginSource) error {
 func DelLoginSource(source *LoginSource) error {
-	cnt, err := orm.Count(&User{LoginSource: source.Id})
+	cnt, err := x.Count(&User{LoginSource: source.Id})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	if cnt > 0 {
 	if cnt > 0 {
 		return ErrAuthenticationUserUsed
 		return ErrAuthenticationUserUsed
 	}
 	}
-	_, err = orm.Id(source.Id).Delete(&LoginSource{})
+	_, err = x.Id(source.Id).Delete(&LoginSource{})
 	return err
 	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
 	var u *User
 	if strings.Contains(uname, "@") {
 	if strings.Contains(uname, "@") {
 		u = &User{Email: uname}
 		u = &User{Email: uname}
@@ -159,19 +156,19 @@ func LoginUser(uname, passwd string) (*User, error) {
 		u = &User{LowerName: strings.ToLower(uname)}
 		u = &User{LowerName: strings.ToLower(uname)}
 	}
 	}
 
 
-	has, err := orm.Get(u)
+	has, err := x.Get(u)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if u.LoginType == LT_NOTYPE {
+	if u.LoginType == NOTYPE {
 		if has {
 		if has {
-			u.LoginType = LT_PLAIN
+			u.LoginType = PLAIN
 		}
 		}
 	}
 	}
 
 
 	// for plain login, user must have existed.
 	// for plain login, user must have existed.
-	if u.LoginType == LT_PLAIN {
+	if u.LoginType == PLAIN {
 		if !has {
 		if !has {
 			return nil, ErrUserNotExist
 			return nil, ErrUserNotExist
 		}
 		}
@@ -185,28 +182,26 @@ func LoginUser(uname, passwd string) (*User, error) {
 	} else {
 	} else {
 		if !has {
 		if !has {
 			var sources []LoginSource
 			var sources []LoginSource
-			if err = orm.UseBool().Find(&sources,
+			if err = x.UseBool().Find(&sources,
 				&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
 				&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 
 
 			for _, source := range sources {
 			for _, source := range sources {
-				if source.Type == LT_LDAP {
+				if source.Type == LDAP {
 					u, err := LoginUserLdapSource(nil, uname, passwd,
 					u, err := LoginUserLdapSource(nil, uname, passwd,
 						source.Id, source.Cfg.(*LDAPConfig), true)
 						source.Id, source.Cfg.(*LDAPConfig), true)
 					if err == nil {
 					if err == nil {
 						return u, 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,
 					u, err := LoginUserSMTPSource(nil, uname, passwd,
 						source.Id, source.Cfg.(*SMTPConfig), true)
 						source.Id, source.Cfg.(*SMTPConfig), true)
 					if err == nil {
 					if err == nil {
 						return u, 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
 		var source LoginSource
-		hasSource, err := orm.Id(u.LoginSource).Get(&source)
+		hasSource, err := x.Id(u.LoginSource).Get(&source)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		} else if !hasSource {
 		} else if !hasSource {
@@ -224,10 +219,10 @@ func LoginUser(uname, passwd string) (*User, error) {
 		}
 		}
 
 
 		switch u.LoginType {
 		switch u.LoginType {
-		case LT_LDAP:
+		case LDAP:
 			return LoginUserLdapSource(u, u.LoginName, passwd,
 			return LoginUserLdapSource(u, u.LoginName, passwd,
 				source.Id, source.Cfg.(*LDAPConfig), false)
 				source.Id, source.Cfg.(*LDAPConfig), false)
-		case LT_SMTP:
+		case SMTP:
 			return LoginUserSMTPSource(u, u.LoginName, passwd,
 			return LoginUserSMTPSource(u, u.LoginName, passwd,
 				source.Id, source.Cfg.(*SMTPConfig), false)
 				source.Id, source.Cfg.(*SMTPConfig), false)
 		}
 		}
@@ -252,7 +247,7 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L
 	user = &User{
 	user = &User{
 		LowerName:   strings.ToLower(name),
 		LowerName:   strings.ToLower(name),
 		Name:        strings.ToLower(name),
 		Name:        strings.ToLower(name),
-		LoginType:   LT_LDAP,
+		LoginType:   LDAP,
 		LoginSource: sourceId,
 		LoginSource: sourceId,
 		LoginName:   name,
 		LoginName:   name,
 		IsActive:    true,
 		IsActive:    true,
@@ -260,7 +255,7 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L
 		Email:       mail,
 		Email:       mail,
 	}
 	}
 
 
-	return RegisterUser(user)
+	return CreateUser(user)
 }
 }
 
 
 type loginAuth struct {
 type loginAuth struct {
@@ -320,9 +315,8 @@ func SmtpAuth(host string, port int, a smtp.Auth, useTls bool) error {
 			return err
 			return err
 		}
 		}
 		return nil
 		return nil
-	} else {
-		return ErrUnsupportedLoginType
 	}
 	}
+	return ErrUnsupportedLoginType
 }
 }
 
 
 // Query if name/passwd can login against the LDAP direcotry pool
 // 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{
 	user = &User{
 		LowerName:   strings.ToLower(loginName),
 		LowerName:   strings.ToLower(loginName),
 		Name:        strings.ToLower(loginName),
 		Name:        strings.ToLower(loginName),
-		LoginType:   LT_SMTP,
+		LoginType:   SMTP,
 		LoginSource: sourceId,
 		LoginSource: sourceId,
 		LoginName:   name,
 		LoginName:   name,
 		IsActive:    true,
 		IsActive:    true,
 		Passwd:      passwd,
 		Passwd:      passwd,
 		Email:       name,
 		Email:       name,
 	}
 	}
-
-	return RegisterUser(user)
+	return CreateUser(user)
 }
 }

+ 28 - 27
models/models.go

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

+ 16 - 16
models/oauth2.go

@@ -8,16 +8,16 @@ import (
 	"errors"
 	"errors"
 )
 )
 
 
-// OT: Oauth2 Type
+type OauthType int
+
 const (
 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 (
 var (
@@ -35,18 +35,18 @@ type Oauth2 struct {
 }
 }
 
 
 func BindUserOauth2(userId, oauthId int64) error {
 func BindUserOauth2(userId, oauthId int64) error {
-	_, err := orm.Id(oauthId).Update(&Oauth2{Uid: userId})
+	_, err := x.Id(oauthId).Update(&Oauth2{Uid: userId})
 	return err
 	return err
 }
 }
 
 
 func AddOauth2(oa *Oauth2) error {
 func AddOauth2(oa *Oauth2) error {
-	_, err := orm.Insert(oa)
+	_, err := x.Insert(oa)
 	return err
 	return err
 }
 }
 
 
 func GetOauth2(identity string) (oa *Oauth2, err error) {
 func GetOauth2(identity string) (oa *Oauth2, err error) {
 	oa = &Oauth2{Identity: identity}
 	oa = &Oauth2{Identity: identity}
-	isExist, err := orm.Get(oa)
+	isExist, err := x.Get(oa)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	} else if !isExist {
 	} else if !isExist {
@@ -60,7 +60,7 @@ func GetOauth2(identity string) (oa *Oauth2, err error) {
 
 
 func GetOauth2ById(id int64) (oa *Oauth2, err error) {
 func GetOauth2ById(id int64) (oa *Oauth2, err error) {
 	oa = new(Oauth2)
 	oa = new(Oauth2)
-	has, err := orm.Id(id).Get(oa)
+	has, err := x.Id(id).Get(oa)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} 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.
 // GetOauthByUserId returns list of oauthes that are releated to given user.
 func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) {
 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
 	return oas, err
 }
 }
 
 
 // DeleteOauth2ById deletes a oauth2 by ID.
 // DeleteOauth2ById deletes a oauth2 by ID.
 func DeleteOauth2ById(id int64) error {
 func DeleteOauth2ById(id int64) error {
-	_, err := orm.Delete(&Oauth2{Id: id})
+	_, err := x.Delete(&Oauth2{Id: id})
 	return err
 	return err
 }
 }
 
 
 // CleanUnbindOauth deletes all unbind OAuthes.
 // CleanUnbindOauth deletes all unbind OAuthes.
 func CleanUnbindOauth() error {
 func CleanUnbindOauth() error {
-	_, err := orm.Delete(&Oauth2{Uid: -1})
+	_, err := x.Delete(&Oauth2{Uid: -1})
 	return err
 	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"
 	"time"
 
 
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/com"
-	qlog "github.com/qiniu/log"
 
 
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/process"
 )
 )
 
 
 const (
 const (
@@ -37,7 +37,7 @@ var (
 var sshOpLocker = sync.Mutex{}
 var sshOpLocker = sync.Mutex{}
 
 
 var (
 var (
-	sshPath string // SSH directory.
+	SshPath string // SSH directory.
 	appPath string // Execution(binary) path.
 	appPath string // Execution(binary) path.
 )
 )
 
 
@@ -54,7 +54,7 @@ func exePath() (string, error) {
 func homeDir() string {
 func homeDir() string {
 	home, err := com.HomeDir()
 	home, err := com.HomeDir()
 	if err != nil {
 	if err != nil {
-		qlog.Fatalln(err)
+		log.Fatal("Fail to get home directory: %v", err)
 	}
 	}
 	return home
 	return home
 }
 }
@@ -63,13 +63,13 @@ func init() {
 	var err error
 	var err error
 
 
 	if appPath, err = exePath(); err != nil {
 	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.
 	// 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()
 	sshOpLocker.Lock()
 	defer sshOpLocker.Unlock()
 	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)
 	f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -107,7 +107,7 @@ func saveAuthorizedKeyFile(key *PublicKey) error {
 
 
 // AddPublicKey adds new public key to database and authorized_keys file.
 // AddPublicKey adds new public key to database and authorized_keys file.
 func AddPublicKey(key *PublicKey) (err error) {
 func AddPublicKey(key *PublicKey) (err error) {
-	has, err := orm.Get(key)
+	has, err := x.Get(key)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	} else if has {
 	} 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 {
 	if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
 		return err
 		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 {
 	if err != nil {
 		return errors.New("ssh-keygen -l -f: " + stderr)
 		return errors.New("ssh-keygen -l -f: " + stderr)
 	} else if len(stdout) < 2 {
 	} else if len(stdout) < 2 {
@@ -130,11 +130,11 @@ func AddPublicKey(key *PublicKey) (err error) {
 	key.Fingerprint = strings.Split(stdout, " ")[1]
 	key.Fingerprint = strings.Split(stdout, " ")[1]
 
 
 	// Save SSH key.
 	// Save SSH key.
-	if _, err = orm.Insert(key); err != nil {
+	if _, err = x.Insert(key); err != nil {
 		return err
 		return err
 	} else if err = saveAuthorizedKeyFile(key); err != nil {
 	} else if err = saveAuthorizedKeyFile(key); err != nil {
 		// Roll back.
 		// Roll back.
-		if _, err2 := orm.Delete(key); err2 != nil {
+		if _, err2 := x.Delete(key); err2 != nil {
 			return err2
 			return err2
 		}
 		}
 		return err
 		return err
@@ -146,7 +146,7 @@ func AddPublicKey(key *PublicKey) (err error) {
 // ListPublicKey returns a list of all public keys that user has.
 // ListPublicKey returns a list of all public keys that user has.
 func ListPublicKey(uid int64) ([]PublicKey, error) {
 func ListPublicKey(uid int64) ([]PublicKey, error) {
 	keys := make([]PublicKey, 0, 5)
 	keys := make([]PublicKey, 0, 5)
-	err := orm.Find(&keys, &PublicKey{OwnerId: uid})
+	err := x.Find(&keys, &PublicKey{OwnerId: uid})
 	return keys, err
 	return keys, err
 }
 }
 
 
@@ -161,7 +161,7 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
 	}
 	}
 	defer fr.Close()
 	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 {
 	if err != nil {
 		return err
 		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.
 // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
 func DeletePublicKey(key *PublicKey) error {
 func DeletePublicKey(key *PublicKey) error {
-	has, err := orm.Get(key)
+	has, err := x.Get(key)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	} else if !has {
 	} else if !has {
 		return ErrKeyNotExist
 		return ErrKeyNotExist
 	}
 	}
 
 
-	if _, err = orm.Delete(key); err != nil {
+	if _, err = x.Delete(key); err != nil {
 		return err
 		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)
 	log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath)
 
 
 	if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
 	if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {

+ 92 - 29
models/release.go

@@ -6,15 +6,16 @@ package models
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"sort"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"github.com/Unknwon/com"
 	"github.com/gogits/git"
 	"github.com/gogits/git"
 )
 )
 
 
 var (
 var (
 	ErrReleaseAlreadyExist = errors.New("Release already exist")
 	ErrReleaseAlreadyExist = errors.New("Release already exist")
+	ErrReleaseNotExist     = errors.New("Release does not exist")
 )
 )
 
 
 // Release represents a release of repository.
 // Release represents a release of repository.
@@ -23,21 +24,17 @@ type Release struct {
 	RepoId           int64
 	RepoId           int64
 	PublisherId      int64
 	PublisherId      int64
 	Publisher        *User `xorm:"-"`
 	Publisher        *User `xorm:"-"`
-	Title            string
 	TagName          string
 	TagName          string
 	LowerTagName     string
 	LowerTagName     string
-	SHA1             string
+	Target           string
+	Title            string
+	Sha1             string `xorm:"VARCHAR(40)"`
 	NumCommits       int
 	NumCommits       int
 	NumCommitsBehind int    `xorm:"-"`
 	NumCommitsBehind int    `xorm:"-"`
 	Note             string `xorm:"TEXT"`
 	Note             string `xorm:"TEXT"`
+	IsDraft          bool   `xorm:"NOT NULL DEFAULT false"`
 	IsPrerelease     bool
 	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.
 // 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 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.
 // CreateRelease creates a new release of repository.
@@ -58,26 +82,65 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
 		return ErrReleaseAlreadyExist
 		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
 	return err
 }
 }

+ 273 - 199
models/repo.go

@@ -9,9 +9,9 @@ import (
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
-	"os/exec"
 	"path"
 	"path"
 	"path/filepath"
 	"path/filepath"
+	"sort"
 	"strings"
 	"strings"
 	"time"
 	"time"
 	"unicode/utf8"
 	"unicode/utf8"
@@ -24,9 +24,14 @@ import (
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/bin"
 	"github.com/gogits/gogs/modules/bin"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/process"
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
+const (
+	TPL_UPDATE_HOOK = "#!/usr/bin/env %s\n%s update $1 $2 $3\n"
+)
+
 var (
 var (
 	ErrRepoAlreadyExist  = errors.New("Repository already exist")
 	ErrRepoAlreadyExist  = errors.New("Repository already exist")
 	ErrRepoNotExist      = errors.New("Repository does not exist")
 	ErrRepoNotExist      = errors.New("Repository does not exist")
@@ -75,19 +80,21 @@ func LoadRepoConfig() {
 
 
 	LanguageIgns = typeFiles[0]
 	LanguageIgns = typeFiles[0]
 	Licenses = typeFiles[1]
 	Licenses = typeFiles[1]
+	sort.Strings(LanguageIgns)
+	sort.Strings(Licenses)
 }
 }
 
 
 func NewRepoContext() {
 func NewRepoContext() {
 	zip.Verbose = false
 	zip.Verbose = false
 
 
 	// Check if server has basic git setting.
 	// 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:") {
 	if strings.Contains(stderr, "fatal:") {
 		log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr)
 		log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr)
 	} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
 	} 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)
 			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)
 			log.Fatal("repo.NewRepoContext(fail to set git user.name): %s", stderr)
 		}
 		}
 	}
 	}
@@ -106,11 +113,11 @@ func NewRepoContext() {
 // Repository represents a git repository.
 // Repository represents a git repository.
 type Repository struct {
 type Repository struct {
 	Id                  int64
 	Id                  int64
-	OwnerId             int64 `xorm:"unique(s)"`
+	OwnerId             int64 `xorm:"UNIQUE(s)"`
 	Owner               *User `xorm:"-"`
 	Owner               *User `xorm:"-"`
 	ForkId              int64
 	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
 	Description         string
 	Website             string
 	Website             string
 	NumWatches          int
 	NumWatches          int
@@ -128,8 +135,8 @@ type Repository struct {
 	IsBare              bool
 	IsBare              bool
 	IsGoget             bool
 	IsGoget             bool
 	DefaultBranch       string
 	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) {
 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.
 // IsRepositoryExist returns true if the repository with given name under user has already existed.
 func IsRepositoryExist(u *User, repoName string) (bool, error) {
 func IsRepositoryExist(u *User, repoName string) (bool, error) {
 	repo := Repository{OwnerId: u.Id}
 	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 {
 	if err != nil {
 		return has, err
 		return has, err
 	} else if !has {
 	} else if !has {
@@ -151,7 +158,7 @@ func IsRepositoryExist(u *User, repoName string) (bool, error) {
 }
 }
 
 
 var (
 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"}
 	illegalSuffixs = []string{".git"}
 )
 )
 
 
@@ -181,9 +188,30 @@ type Mirror struct {
 	NextUpdate time.Time
 	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) {
 func GetMirror(repoId int64) (*Mirror, error) {
 	m := &Mirror{RepoId: repoId}
 	m := &Mirror{RepoId: repoId}
-	has, err := orm.Get(m)
+	has, err := x.Get(m)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -193,24 +221,26 @@ func GetMirror(repoId int64) (*Mirror, error) {
 }
 }
 
 
 func UpdateMirror(m *Mirror) error {
 func UpdateMirror(m *Mirror) error {
-	_, err := orm.Id(m.Id).Update(m)
+	_, err := x.Id(m.Id).Update(m)
 	return err
 	return err
 }
 }
 
 
 // MirrorUpdate checks and updates mirror repositories.
 // MirrorUpdate checks and updates mirror repositories.
 func MirrorUpdate() {
 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)
 		m := bean.(*Mirror)
 		if m.NextUpdate.After(time.Now()) {
 		if m.NextUpdate.After(time.Now()) {
 			return nil
 			return nil
 		}
 		}
 
 
+		// TODO: need timeout.
 		repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
 		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)
 			return errors.New("git remote update: " + stderr)
 		} else if err = git.UnpackRefs(repoPath); err != nil {
 		} 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)
 		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.
 // 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 {
 	if err != nil {
 		return nil, err
 		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()))
 	tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
 	os.MkdirAll(tmpDir, os.ModePerm)
 	os.MkdirAll(tmpDir, os.ModePerm)
 
 
-	repoPath := RepoPath(user.Name, name)
+	repoPath := RepoPath(u.Name, name)
 
 
 	repo.IsBare = false
 	repo.IsBare = false
 	if mirror {
 	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
 			return repo, err
 		}
 		}
 		repo.IsMirror = true
 		repo.IsMirror = true
 		return repo, UpdateRepository(repo)
 		return repo, UpdateRepository(repo)
 	}
 	}
 
 
+	// TODO: need timeout.
 	// Clone from local repository.
 	// 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 {
 	if err != nil {
 		return repo, errors.New("git clone: " + stderr)
 		return repo, errors.New("git clone: " + stderr)
 	}
 	}
 
 
+	// TODO: need timeout.
 	// Pull data from source.
 	// 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)
 		return repo, errors.New("git pull: " + stderr)
 	}
 	}
 
 
+	// TODO: need timeout.
 	// Push data to local repository.
 	// 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, errors.New("git push: " + stderr)
 	}
 	}
 
 
 	return repo, UpdateRepository(repo)
 	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.
 // extractGitBareZip extracts git-bare.zip to repository path.
 func extractGitBareZip(repoPath string) error {
 func extractGitBareZip(repoPath string) error {
 	z, err := zip.Open(filepath.Join(setting.RepoRootPath, "git-bare.zip"))
 	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.
 // initRepoCommit temporarily changes with work directory.
 func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 	var stderr string
 	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)
 		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 {
 		"-m", "Init commit"); err != nil {
 		return errors.New("git commit: " + stderr)
 		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 errors.New("git push: " + stderr)
 	}
 	}
 	return nil
 	return nil
@@ -447,7 +366,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	rp := strings.NewReplacer("\\", "/", " ", "\\ ")
 	rp := strings.NewReplacer("\\", "/", " ", "\\ ")
 	// hook/post-update
 	// hook/post-update
 	if err := createHookUpdate(filepath.Join(repoPath, "hooks", "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 {
 			rp.Replace(appPath))); err != nil {
 		return err
 		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()))
 	tmpDir := filepath.Join(os.TempDir(), base.ToStr(time.Now().Nanosecond()))
 	os.MkdirAll(tmpDir, os.ModePerm)
 	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 {
 	if err != nil {
-		return errors.New("git clone: " + stderr)
+		return errors.New("initRepository(git clone): " + stderr)
 	}
 	}
 
 
 	// README
 	// README
@@ -486,22 +407,40 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	// .gitignore
 	// .gitignore
 	if repoLang != "" {
 	if repoLang != "" {
 		filePath := "conf/gitignore/" + 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
 				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
 	// LICENSE
 	if license != "" {
 	if license != "" {
 		filePath := "conf/license/" + 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
 				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())
 	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.
 // GetRepositoriesWithUsers returns given number of repository objects with offset.
 // It also auto-gets corresponding users.
 // It also auto-gets corresponding users.
 func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
 func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
 	repos := make([]*Repository, 0, num)
 	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
 		return nil, err
 	}
 	}
 
 
 	for _, repo := range repos {
 	for _, repo := range repos {
 		repo.Owner = &User{Id: repo.OwnerId}
 		repo.Owner = &User{Id: repo.OwnerId}
-		has, err := orm.Get(repo.Owner)
+		has, err := x.Get(repo.Owner)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		} else if !has {
 		} else if !has {
@@ -550,11 +628,11 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error
 
 
 	// Update accesses.
 	// Update accesses.
 	accesses := make([]Access, 0, 10)
 	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
 		return err
 	}
 	}
 
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -615,11 +693,11 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error
 func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
 func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
 	// Update accesses.
 	// Update accesses.
 	accesses := make([]Access, 0, 10)
 	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
 		return err
 	}
 	}
 
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -650,25 +728,26 @@ func UpdateRepository(repo *Repository) error {
 	if len(repo.Website) > 255 {
 	if len(repo.Website) > 255 {
 		repo.Website = 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
 	return err
 }
 }
 
 
 // DeleteRepository deletes a repository for a user or orgnaztion.
 // 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}
 	repo := &Repository{Id: repoId, OwnerId: userId}
-	has, err := orm.Get(repo)
+	has, err := x.Get(repo)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	} else if !has {
 	} else if !has {
 		return ErrRepoNotExist
 		return ErrRepoNotExist
 	}
 	}
 
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
 	}
 	}
+
 	if _, err = sess.Delete(&Repository{Id: repoId}); err != nil {
 	if _, err = sess.Delete(&Repository{Id: repoId}); err != nil {
 		sess.Rollback()
 		sess.Rollback()
 		return err
 		return err
@@ -703,7 +782,7 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
 	}
 	}
 
 
 	// Delete comments.
 	// 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)
 		issue := bean.(*Issue)
 		if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
 		if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
 			sess.Rollback()
 			sess.Rollback()
@@ -725,16 +804,11 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
 		sess.Rollback()
 		sess.Rollback()
 		return err
 		return err
 	}
 	}
-	if err = sess.Commit(); err != nil {
-		sess.Rollback()
-		return err
-	}
 	if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
 	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 err
 	}
 	}
-	return nil
+	return sess.Commit()
 }
 }
 
 
 // GetRepositoryByName returns the repository by given name under user if exists.
 // 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,
 		OwnerId:   userId,
 		LowerName: strings.ToLower(repoName),
 		LowerName: strings.ToLower(repoName),
 	}
 	}
-	has, err := orm.Get(repo)
+	has, err := x.Get(repo)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -755,7 +829,7 @@ func GetRepositoryByName(userId int64, repoName string) (*Repository, error) {
 // GetRepositoryById returns the repository by given id if exists.
 // GetRepositoryById returns the repository by given id if exists.
 func GetRepositoryById(id int64) (*Repository, error) {
 func GetRepositoryById(id int64) (*Repository, error) {
 	repo := &Repository{}
 	repo := &Repository{}
-	has, err := orm.Id(id).Get(repo)
+	has, err := x.Id(id).Get(repo)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -767,7 +841,7 @@ func GetRepositoryById(id int64) (*Repository, error) {
 // GetRepositories returns a list of repositories of given user.
 // GetRepositories returns a list of repositories of given user.
 func GetRepositories(uid int64, private bool) ([]*Repository, error) {
 func GetRepositories(uid int64, private bool) ([]*Repository, error) {
 	repos := make([]*Repository, 0, 10)
 	repos := make([]*Repository, 0, 10)
-	sess := orm.Desc("updated")
+	sess := x.Desc("updated")
 	if !private {
 	if !private {
 		sess.Where("is_private=?", false)
 		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.
 // GetRecentUpdatedRepositories returns the list of repositories that are recently updated.
 func GetRecentUpdatedRepositories() (repos []*Repository, err error) {
 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
 	return repos, err
 }
 }
 
 
 // GetRepositoryCount returns the total number of repositories of user.
 // GetRepositoryCount returns the total number of repositories of user.
 func GetRepositoryCount(user *User) (int64, error) {
 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.
 // GetCollaboratorNames returns a list of user name of repository's collaborators.
 func GetCollaboratorNames(repoName string) ([]string, error) {
 func GetCollaboratorNames(repoName string) ([]string, error) {
 	accesses := make([]*Access, 0, 10)
 	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
 		return nil, err
 	}
 	}
 
 
@@ -805,17 +879,17 @@ func GetCollaboratorNames(repoName string) ([]string, error) {
 func GetCollaborativeRepos(uname string) ([]*Repository, error) {
 func GetCollaborativeRepos(uname string) ([]*Repository, error) {
 	uname = strings.ToLower(uname)
 	uname = strings.ToLower(uname)
 	accesses := make([]*Access, 0, 10)
 	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
 		return nil, err
 	}
 	}
 
 
 	repos := make([]*Repository, 0, 10)
 	repos := make([]*Repository, 0, 10)
 	for _, access := range accesses {
 	for _, access := range accesses {
-		if strings.HasPrefix(access.RepoName, uname) {
+		infos := strings.Split(access.RepoName, "/")
+		if infos[0] == uname {
 			continue
 			continue
 		}
 		}
 
 
-		infos := strings.Split(access.RepoName, "/")
 		u, err := GetUserByName(infos[0])
 		u, err := GetUserByName(infos[0])
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
@@ -834,7 +908,7 @@ func GetCollaborativeRepos(uname string) ([]*Repository, error) {
 // GetCollaborators returns a list of users of repository's collaborators.
 // GetCollaborators returns a list of users of repository's collaborators.
 func GetCollaborators(repoName string) (us []*User, err error) {
 func GetCollaborators(repoName string) (us []*User, err error) {
 	accesses := make([]*Access, 0, 10)
 	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
 		return nil, err
 	}
 	}
 
 
@@ -858,18 +932,18 @@ type Watch struct {
 // Watch or unwatch repository.
 // Watch or unwatch repository.
 func WatchRepo(uid, rid int64, watch bool) (err error) {
 func WatchRepo(uid, rid int64, watch bool) (err error) {
 	if watch {
 	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
 			return err
 		}
 		}
 
 
 		rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
 		rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
-		_, err = orm.Exec(rawSql, rid)
+		_, err = x.Exec(rawSql, rid)
 	} else {
 	} else {
-		if _, err = orm.Delete(&Watch{0, uid, rid}); err != nil {
+		if _, err = x.Delete(&Watch{0, uid, rid}); err != nil {
 			return err
 			return err
 		}
 		}
 		rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?"
 		rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?"
-		_, err = orm.Exec(rawSql, rid)
+		_, err = x.Exec(rawSql, rid)
 	}
 	}
 	return err
 	return err
 }
 }
@@ -877,7 +951,7 @@ func WatchRepo(uid, rid int64, watch bool) (err error) {
 // GetWatchers returns all watchers of given repository.
 // GetWatchers returns all watchers of given repository.
 func GetWatchers(rid int64) ([]*Watch, error) {
 func GetWatchers(rid int64) ([]*Watch, error) {
 	watches := make([]*Watch, 0, 10)
 	watches := make([]*Watch, 0, 10)
-	err := orm.Find(&watches, &Watch{RepoId: rid})
+	err := x.Find(&watches, &Watch{RepoId: rid})
 	return watches, err
 	return watches, err
 }
 }
 
 
@@ -891,7 +965,7 @@ func NotifyWatchers(act *Action) error {
 
 
 	// Add feed for actioner.
 	// Add feed for actioner.
 	act.UserId = act.ActUserId
 	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())
 		return errors.New("repo.NotifyWatchers(create action): " + err.Error())
 	}
 	}
 
 
@@ -902,7 +976,7 @@ func NotifyWatchers(act *Action) error {
 
 
 		act.Id = 0
 		act.Id = 0
 		act.UserId = watches[i].UserId
 		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())
 			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.
 // IsWatching checks if user has watched given repository.
 func IsWatching(uid, rid int64) bool {
 func IsWatching(uid, rid int64) bool {
-	has, _ := orm.Get(&Watch{0, uid, rid})
+	has, _ := x.Get(&Watch{0, uid, rid})
 	return has
 	return has
 }
 }
 
 

+ 15 - 16
models/update.go

@@ -6,21 +6,21 @@ package models
 
 
 import (
 import (
 	"container/list"
 	"container/list"
+	"fmt"
 	"os/exec"
 	"os/exec"
 	"strings"
 	"strings"
 
 
-	qlog "github.com/qiniu/log"
-
 	"github.com/gogits/git"
 	"github.com/gogits/git"
 
 
 	"github.com/gogits/gogs/modules/base"
 	"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")
 	isNew := strings.HasPrefix(oldCommitId, "0000000")
 	if isNew &&
 	if isNew &&
 		strings.HasPrefix(newCommitId, "0000000") {
 		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)
 	f := RepoPath(repoUserName, repoName)
@@ -31,19 +31,18 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
 
 
 	isDel := strings.HasPrefix(newCommitId, "0000000")
 	isDel := strings.HasPrefix(newCommitId, "0000000")
 	if isDel {
 	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)
 	repo, err := git.OpenRepository(f)
 	if err != nil {
 	if err != nil {
-		qlog.Fatalf("runUpdate.Open repoId: %v", err)
+		return fmt.Errorf("runUpdate.Open repoId: %v", err)
 	}
 	}
 
 
 	newCommit, err := repo.GetCommit(newCommitId)
 	newCommit, err := repo.GetCommit(newCommitId)
 	if err != nil {
 	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
 	var l *list.List
@@ -51,28 +50,27 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
 	if isNew {
 	if isNew {
 		l, err = newCommit.CommitsBefore()
 		l, err = newCommit.CommitsBefore()
 		if err != nil {
 		if err != nil {
-			qlog.Fatalf("Find CommitsBefore erro: %v", err)
+			return fmt.Errorf("Find CommitsBefore erro: %v", err)
 		}
 		}
 	} else {
 	} else {
 		l, err = newCommit.CommitsBeforeUntil(oldCommitId)
 		l, err = newCommit.CommitsBeforeUntil(oldCommitId)
 		if err != nil {
 		if err != nil {
-			qlog.Fatalf("Find CommitsBeforeUntil erro: %v", err)
-			return
+			return fmt.Errorf("Find CommitsBeforeUntil erro: %v", err)
 		}
 		}
 	}
 	}
 
 
 	if err != nil {
 	if err != nil {
-		qlog.Fatalf("runUpdate.Commit repoId: %v", err)
+		return fmt.Errorf("runUpdate.Commit repoId: %v", err)
 	}
 	}
 
 
 	ru, err := GetUserByName(repoUserName)
 	ru, err := GetUserByName(repoUserName)
 	if err != nil {
 	if err != nil {
-		qlog.Fatalf("runUpdate.GetUserByName: %v", err)
+		return fmt.Errorf("runUpdate.GetUserByName: %v", err)
 	}
 	}
 
 
 	repos, err := GetRepositoryByName(ru.Id, repoName)
 	repos, err := GetRepositoryByName(ru.Id, repoName)
 	if err != nil {
 	if err != nil {
-		qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
+		return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
 	}
 	}
 
 
 	commits := make([]*base.PushCommit, 0)
 	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()})
 	//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
 	if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
 	if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
 		repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
 		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"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
-// User types.
+type UserType int
+
 const (
 const (
-	UT_INDIVIDUAL = iota + 1
-	UT_ORGANIZATION
+	INDIVIDUAL UserType = iota // Historic reason to make it starts at 0.
+	ORGANIZATION
 )
 )
 
 
 var (
 var (
 	ErrUserOwnRepos          = errors.New("User still have ownership of repositories")
 	ErrUserOwnRepos          = errors.New("User still have ownership of repositories")
+	ErrUserHasOrgs           = errors.New("User still have membership of organization")
 	ErrUserAlreadyExist      = errors.New("User already exist")
 	ErrUserAlreadyExist      = errors.New("User already exist")
 	ErrUserNotExist          = errors.New("User does not exist")
 	ErrUserNotExist          = errors.New("User does not exist")
 	ErrUserNotKeyOwner       = errors.New("User does not the owner of public key")
 	ErrUserNotKeyOwner       = errors.New("User does not the owner of public key")
@@ -47,10 +49,11 @@ type User struct {
 	FullName      string
 	FullName      string
 	Email         string `xorm:"unique not null"`
 	Email         string `xorm:"unique not null"`
 	Passwd        string `xorm:"not null"`
 	Passwd        string `xorm:"not null"`
-	LoginType     int
+	LoginType     LoginType
 	LoginSource   int64 `xorm:"not null default 0"`
 	LoginSource   int64 `xorm:"not null default 0"`
 	LoginName     string
 	LoginName     string
-	Type          int
+	Type          UserType
+	Orgs          []*User `xorm:"-"`
 	NumFollowers  int
 	NumFollowers  int
 	NumFollowings int
 	NumFollowings int
 	NumStars      int
 	NumStars      int
@@ -65,43 +68,63 @@ type User struct {
 	Salt          string    `xorm:"VARCHAR(10)"`
 	Salt          string    `xorm:"VARCHAR(10)"`
 	Created       time.Time `xorm:"created"`
 	Created       time.Time `xorm:"created"`
 	Updated       time.Time `xorm:"updated"`
 	Updated       time.Time `xorm:"updated"`
+
+	// For organization.
+	Description string
+	NumTeams    int
+	NumMembers  int
 }
 }
 
 
 // HomeLink returns the user home page link.
 // 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 {
 	if setting.DisableGravatar {
 		return "/img/avatar_default.jpg"
 		return "/img/avatar_default.jpg"
 	} else if setting.Service.EnableCacheAvatar {
 	} 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.
 // NewGitSig generates and returns the signature of given user.
-func (user *User) NewGitSig() *git.Signature {
+func (u *User) NewGitSig() *git.Signature {
 	return &git.Signature{
 	return &git.Signature{
-		Name:  user.Name,
-		Email: user.Email,
+		Name:  u.Name,
+		Email: u.Email,
 		When:  time.Now(),
 		When:  time.Now(),
 	}
 	}
 }
 }
 
 
 // EncodePasswd encodes password to safe format.
 // 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,
 // IsUserExist checks if given user name exist,
@@ -110,7 +133,7 @@ func IsUserExist(name string) (bool, error) {
 	if len(name) == 0 {
 	if len(name) == 0 {
 		return false, nil
 		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.
 // IsEmailUsed returns true if the e-mail has been used.
@@ -118,7 +141,7 @@ func IsEmailUsed(email string) (bool, error) {
 	if len(email) == 0 {
 	if len(email) == 0 {
 		return false, nil
 		return false, nil
 	}
 	}
-	return orm.Get(&User{Email: email})
+	return x.Get(&User{Email: email})
 }
 }
 
 
 // GetUserSalt returns a user salt token
 // GetUserSalt returns a user salt token
@@ -126,55 +149,66 @@ func GetUserSalt() string {
 	return base.GetRandomString(10)
 	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
 		return nil, ErrUserNameIllegal
 	}
 	}
 
 
-	isExist, err := IsUserExist(user.Name)
+	isExist, err := IsUserExist(u.Name)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if isExist {
 	} else if isExist {
 		return nil, ErrUserAlreadyExist
 		return nil, ErrUserAlreadyExist
 	}
 	}
 
 
-	isExist, err = IsEmailUsed(user.Email)
+	isExist, err = IsEmailUsed(u.Email)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if isExist {
 	} else if isExist {
 		return nil, ErrEmailAlreadyUsed
 		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
 		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
 		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.
 // GetUsers returns given number of user objects with offset.
 func GetUsers(num, offset int) ([]User, error) {
 func GetUsers(num, offset int) ([]User, error) {
 	users := make([]User, 0, num)
 	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
 	return users, err
 }
 }
 
 
@@ -218,11 +252,11 @@ func ChangeUserName(user *User, newUserName string) (err error) {
 
 
 	// Update accesses of user.
 	// Update accesses of user.
 	accesses := make([]Access, 0, 10)
 	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
 		return err
 	}
 	}
 
 
-	sess := orm.NewSession()
+	sess := x.NewSession()
 	defer sess.Close()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 	if err = sess.Begin(); err != nil {
 		return err
 		return err
@@ -245,7 +279,7 @@ func ChangeUserName(user *User, newUserName string) (err error) {
 	for i := range repos {
 	for i := range repos {
 		accesses = make([]Access, 0, 10)
 		accesses = make([]Access, 0, 10)
 		// Update accesses of user repository.
 		// 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
 			return err
 		}
 		}
 
 
@@ -268,60 +302,68 @@ func ChangeUserName(user *User, newUserName string) (err error) {
 }
 }
 
 
 // UpdateUser updates user's information.
 // 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
 	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.
 	// Check ownership of repository.
-	count, err := GetRepositoryCount(user)
+	count, err := GetRepositoryCount(u)
 	if err != nil {
 	if err != nil {
-		return errors.New("modesl.GetRepositories: " + err.Error())
+		return errors.New("modesl.GetRepositories(GetRepositoryCount): " + err.Error())
 	} else if count > 0 {
 	} else if count > 0 {
 		return ErrUserOwnRepos
 		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: check issues, other repos' commits
+	// TODO: roll backable in some point.
 
 
 	// Delete all followers.
 	// 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
 		return err
 	}
 	}
-
 	// Delete oauth2.
 	// Delete oauth2.
-	if _, err = orm.Delete(&Oauth2{Uid: user.Id}); err != nil {
+	if _, err = x.Delete(&Oauth2{Uid: u.Id}); err != nil {
 		return err
 		return err
 	}
 	}
-
 	// Delete all feeds.
 	// 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
 		return err
 	}
 	}
-
 	// Delete all watches.
 	// 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
 		return err
 	}
 	}
-
 	// Delete all accesses.
 	// 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
 		return err
 	}
 	}
-
 	// Delete all SSH keys.
 	// Delete all SSH keys.
 	keys := make([]*PublicKey, 0, 10)
 	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
 		return err
 	}
 	}
 	for _, key := range keys {
 	for _, key := range keys {
@@ -331,11 +373,17 @@ func DeleteUser(user *User) error {
 	}
 	}
 
 
 	// Delete user directory.
 	// Delete user directory.
-	if err = os.RemoveAll(UserPath(user.Name)); err != nil {
+	if err = os.RemoveAll(UserPath(u.Name)); err != nil {
 		return err
 		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
 	return err
 }
 }
 
 
@@ -347,7 +395,7 @@ func UserPath(userName string) string {
 func GetUserByKeyId(keyId int64) (*User, error) {
 func GetUserByKeyId(keyId int64) (*User, error) {
 	user := new(User)
 	user := new(User)
 	rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?"
 	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 {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -356,17 +404,16 @@ func GetUserByKeyId(keyId int64) (*User, error) {
 	return user, nil
 	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) {
 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 {
 	if err != nil {
 		return nil, err
 		return nil, err
-	}
-	if !has {
+	} else if !has {
 		return nil, ErrUserNotExist
 		return nil, ErrUserNotExist
 	}
 	}
-	return user, nil
+	return u, nil
 }
 }
 
 
 // GetUserByName returns the user object by given name if exists.
 // GetUserByName returns the user object by given name if exists.
@@ -375,7 +422,7 @@ func GetUserByName(name string) (*User, error) {
 		return nil, ErrUserNotExist
 		return nil, ErrUserNotExist
 	}
 	}
 	user := &User{LowerName: strings.ToLower(name)}
 	user := &User{LowerName: strings.ToLower(name)}
-	has, err := orm.Get(user)
+	has, err := x.Get(user)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -416,7 +463,7 @@ func GetUserByEmail(email string) (*User, error) {
 		return nil, ErrUserNotExist
 		return nil, ErrUserNotExist
 	}
 	}
 	user := &User{Email: strings.ToLower(email)}
 	user := &User{Email: strings.ToLower(email)}
-	has, err := orm.Get(user)
+	has, err := x.Get(user)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -440,7 +487,7 @@ func SearchUserByName(key string, limit int) (us []*User, err error) {
 	key = strings.ToLower(key)
 	key = strings.ToLower(key)
 
 
 	us = make([]*User, 0, limit)
 	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
 	return us, err
 }
 }
 
 
@@ -453,7 +500,7 @@ type Follow struct {
 
 
 // FollowUser marks someone be another's follower.
 // FollowUser marks someone be another's follower.
 func FollowUser(userId int64, followId int64) (err error) {
 func FollowUser(userId int64, followId int64) (err error) {
-	session := orm.NewSession()
+	session := x.NewSession()
 	defer session.Close()
 	defer session.Close()
 	session.Begin()
 	session.Begin()
 
 
@@ -478,7 +525,7 @@ func FollowUser(userId int64, followId int64) (err error) {
 
 
 // UnFollowUser unmarks someone be another's follower.
 // UnFollowUser unmarks someone be another's follower.
 func UnFollowUser(userId int64, unFollowId int64) (err error) {
 func UnFollowUser(userId int64, unFollowId int64) (err error) {
-	session := orm.NewSession()
+	session := x.NewSession()
 	defer session.Close()
 	defer session.Close()
 	session.Begin()
 	session.Begin()
 
 

+ 126 - 17
models/webhook.go

@@ -7,29 +7,35 @@ package models
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
+	"time"
 
 
+	"github.com/gogits/gogs/modules/httplib"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
 var (
 var (
 	ErrWebhookNotExist = errors.New("Webhook does not exist")
 	ErrWebhookNotExist = errors.New("Webhook does not exist")
 )
 )
 
 
-// Content types.
+type HookContentType int
+
 const (
 const (
-	CT_JSON = iota + 1
-	CT_FORM
+	JSON HookContentType = iota + 1
+	FORM
 )
 )
 
 
+// HookEvent represents events that will delivery hook.
 type HookEvent struct {
 type HookEvent struct {
 	PushOnly bool `json:"push_only"`
 	PushOnly bool `json:"push_only"`
 }
 }
 
 
+// Webhook represents a web hook object.
 type Webhook struct {
 type Webhook struct {
 	Id          int64
 	Id          int64
 	RepoId      int64
 	RepoId      int64
 	Url         string `xorm:"TEXT"`
 	Url         string `xorm:"TEXT"`
-	ContentType int
+	ContentType HookContentType
 	Secret      string `xorm:"TEXT"`
 	Secret      string `xorm:"TEXT"`
 	Events      string `xorm:"TEXT"`
 	Events      string `xorm:"TEXT"`
 	*HookEvent  `xorm:"-"`
 	*HookEvent  `xorm:"-"`
@@ -37,6 +43,7 @@ type Webhook struct {
 	IsActive    bool
 	IsActive    bool
 }
 }
 
 
+// GetEvent handles conversion from Events to HookEvent.
 func (w *Webhook) GetEvent() {
 func (w *Webhook) GetEvent() {
 	w.HookEvent = &HookEvent{}
 	w.HookEvent = &HookEvent{}
 	if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
 	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)
 	data, err := json.Marshal(w.HookEvent)
 	w.Events = string(data)
 	w.Events = string(data)
 	return err
 	return err
 }
 }
 
 
+// HasPushEvent returns true if hook enbaled push event.
 func (w *Webhook) HasPushEvent() bool {
 func (w *Webhook) HasPushEvent() bool {
 	if w.PushOnly {
 	if w.PushOnly {
 		return true
 		return true
@@ -57,22 +66,16 @@ func (w *Webhook) HasPushEvent() bool {
 	return false
 	return false
 }
 }
 
 
-// CreateWebhook creates new webhook.
+// CreateWebhook creates a new web hook.
 func CreateWebhook(w *Webhook) error {
 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
 	return err
 }
 }
 
 
 // GetWebhookById returns webhook by given ID.
 // GetWebhookById returns webhook by given ID.
 func GetWebhookById(hookId int64) (*Webhook, error) {
 func GetWebhookById(hookId int64) (*Webhook, error) {
 	w := &Webhook{Id: hookId}
 	w := &Webhook{Id: hookId}
-	has, err := orm.Get(w)
+	has, err := x.Get(w)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	} else if !has {
 	} else if !has {
@@ -83,18 +86,124 @@ func GetWebhookById(hookId int64) (*Webhook, error) {
 
 
 // GetActiveWebhooksByRepoId returns all active webhooks of repository.
 // GetActiveWebhooksByRepoId returns all active webhooks of repository.
 func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
 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
 	return ws, err
 }
 }
 
 
 // GetWebhooksByRepoId returns all webhooks of repository.
 // GetWebhooksByRepoId returns all webhooks of repository.
 func GetWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
 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
 	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.
 // DeleteWebhook deletes webhook of repository.
 func DeleteWebhook(hookId int64) error {
 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
 	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 {
 type CreateRepoForm struct {
+	Uid         int64  `form:"uid" binding:"Required"`
 	RepoName    string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	RepoName    string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	Private     bool   `form:"private"`
 	Private     bool   `form:"private"`
-	Description string `form:"desc" binding:"MaxSize(100)"`
+	Description string `form:"desc" binding:"MaxSize(255)"`
 	Language    string `form:"language"`
 	Language    string `form:"language"`
 	License     string `form:"license"`
 	License     string `form:"license"`
 	InitReadme  bool   `form:"initReadme"`
 	InitReadme  bool   `form:"initReadme"`
@@ -47,10 +48,11 @@ type MigrateRepoForm struct {
 	Url          string `form:"url" binding:"Url"`
 	Url          string `form:"url" binding:"Url"`
 	AuthUserName string `form:"auth_username"`
 	AuthUserName string `form:"auth_username"`
 	AuthPasswd   string `form:"auth_password"`
 	AuthPasswd   string `form:"auth_password"`
+	Uid          int64  `form:"uid" binding:"Required"`
 	RepoName     string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	RepoName     string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	Mirror       bool   `form:"mirror"`
 	Mirror       bool   `form:"mirror"`
 	Private      bool   `form:"private"`
 	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 {
 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 {
 type RepoSettingForm struct {
 	RepoName    string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
 	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)"`
 	Website     string `form:"site" binding:"Url;MaxSize(100)"`
 	Branch      string `form:"branch"`
 	Branch      string `form:"branch"`
 	Interval    int    `form:"interval"`
 	Interval    int    `form:"interval"`
@@ -205,14 +207,17 @@ func (f *CreateLabelForm) Validate(errors *binding.Errors, req *http.Request, co
 
 
 type NewReleaseForm struct {
 type NewReleaseForm struct {
 	TagName    string `form:"tag_name" binding:"Required"`
 	TagName    string `form:"tag_name" binding:"Required"`
+	Target     string `form:"tag_target" binding:"Required"`
 	Title      string `form:"title" binding:"Required"`
 	Title      string `form:"title" binding:"Required"`
 	Content    string `form:"content" binding:"Required"`
 	Content    string `form:"content" binding:"Required"`
+	Draft      string `form:"draft"`
 	Prerelease bool   `form:"prerelease"`
 	Prerelease bool   `form:"prerelease"`
 }
 }
 
 
 func (f *NewReleaseForm) Name(field string) string {
 func (f *NewReleaseForm) Name(field string) string {
 	names := map[string]string{
 	names := map[string]string{
 		"TagName": "Tag name",
 		"TagName": "Tag name",
+		"Target":  "Target",
 		"Title":   "Release title",
 		"Title":   "Release title",
 		"Content": "Release content",
 		"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)
 	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
 	validate(errors, data, f)
 	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/base"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/middleware/binding"
 	"github.com/gogits/gogs/modules/middleware/binding"
+	"github.com/gogits/gogs/modules/setting"
 )
 )
 
 
 // SignedInId returns the id of signed in user.
 // 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 {
 	if !models.HasEngine {
 		return 0
 		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
 		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 0
 		}
 		}
-		return s
+		return id
 	}
 	}
 	return 0
 	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.
 // 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
 		return nil
 	}
 	}
 
 
-	user, err := models.GetUserById(id)
+	u, err := models.GetUserById(uid)
 	if err != nil {
 	if err != nil {
 		log.Error("user.SignedInUser: %v", err)
 		log.Error("user.SignedInUser: %v", err)
 		return nil
 		return nil
 	}
 	}
-	return user
+	return u
 }
 }
 
 
 // IsSignedIn check if any user has signed in.
 // 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 {
 type FeedsForm struct {
@@ -87,7 +93,7 @@ func (f *UpdateProfileForm) Name(field string) string {
 	names := map[string]string{
 	names := map[string]string{
 		"UserName": "Username",
 		"UserName": "Username",
 		"Email":    "E-mail address",
 		"Email":    "E-mail address",
-		"Website":  "Website",
+		"Website":  "Website address",
 		"Location": "Location",
 		"Location": "Location",
 		"Avatar":   "Gravatar Email",
 		"Avatar":   "Gravatar Email",
 	}
 	}

+ 1 - 0
modules/base/base.go

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

+ 262 - 247
modules/bin/conf.go

@@ -1,11 +1,11 @@
 package bin
 package bin
 
 
 import (
 import (
-    "bytes"
-    "compress/gzip"
-    "fmt"
-    "io"
-    "strings"
+	"bytes"
+	"compress/gzip"
+	"fmt"
+	"io"
+	"strings"
 )
 )
 
 
 func bindata_read(data []byte, name string) ([]byte, error) {
 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) {
 func conf_app_ini() ([]byte, error) {
 	return bindata_read([]byte{
 	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",
 		"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,
 		0x28, 0x3f, 0xfe, 0xba, 0x0a, 0x40, 0x72, 0xf5, 0xcf, 0xf9, 0x6a, 0x9b,
 		0x11, 0xa6, 0xf9, 0x31, 0xfa, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x01,
 		0x11, 0xa6, 0xf9, 0x31, 0xfa, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x01,
 		0x81, 0x55, 0x99, 0xb6, 0x26, 0x00, 0x00,
 		0x81, 0x55, 0x99, 0xb6, 0x26, 0x00, 0x00,
-		},
+	},
 		"conf/content/git-bare.zip",
 		"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,
 		0x8d, 0x88, 0x90, 0x28, 0xbf, 0x3f, 0xd4, 0xfe, 0xe7, 0x05, 0xbd, 0x28,
 		0xc2, 0x24, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x75, 0xb0, 0x31,
 		0xc2, 0x24, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x75, 0xb0, 0x31,
 		0xf7, 0x04, 0x00, 0x00,
 		0xf7, 0x04, 0x00, 0x00,
-		},
+	},
 		"conf/etc/supervisord.conf",
 		"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,
 		0xb8, 0xa3, 0xb5, 0xe2, 0x2c, 0x81, 0x0c, 0xe2, 0x75, 0xc9, 0xf2, 0x07,
 		0x2f, 0x5e, 0x58, 0x0b, 0x39, 0x3d, 0xa4, 0xf9, 0x3f, 0x00, 0x00, 0xff,
 		0x2f, 0x5e, 0x58, 0x0b, 0x39, 0x3d, 0xa4, 0xf9, 0x3f, 0x00, 0x00, 0xff,
 		0xff, 0x00, 0x96, 0x67, 0x2c, 0x0e, 0x01, 0x00, 0x00,
 		0xff, 0x00, 0x96, 0x67, 0x2c, 0x0e, 0x01, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Android",
 		"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,
 		0xeb, 0x8e, 0x79, 0xeb, 0x31, 0x1d, 0x73, 0xb8, 0xa3, 0x8d, 0x6e, 0xdd,
 		0xea, 0xd7, 0xf5, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xca, 0x54, 0xa9, 0x22,
 		0xea, 0xd7, 0xf5, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xca, 0x54, 0xa9, 0x22,
 		0x8f, 0x00, 0x00, 0x00,
 		0x8f, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/C",
 		"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,
 		0x8b, 0x52, 0xd1, 0xf6, 0x63, 0x0e, 0x6e, 0xd8, 0x98, 0xaa, 0x6a, 0xd8,
 		0xb4, 0xfb, 0xc9, 0x76, 0x55, 0xfd, 0xd7, 0x1f, 0x9f, 0xfe, 0x0b, 0x00,
 		0xb4, 0xfb, 0xc9, 0x76, 0x55, 0xfd, 0xd7, 0x1f, 0x9f, 0xfe, 0x0b, 0x00,
 		0x00, 0xff, 0xff, 0xfe, 0xac, 0xdb, 0x69, 0xf1, 0x05, 0x00, 0x00,
 		0x00, 0xff, 0xff, 0xfe, 0xac, 0xdb, 0x69, 0xf1, 0x05, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/C Sharp",
 		"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,
 		0x5c, 0x92, 0x58, 0x82, 0xa6, 0x32, 0x27, 0x31, 0x13, 0x4c, 0x02, 0x89,
 		0x44, 0x2e, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xe6, 0x21, 0x26,
 		0x44, 0x2e, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xe6, 0x21, 0x26,
 		0x7e, 0x00, 0x00, 0x00,
 		0x7e, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/C++",
 		"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,
 		0x22, 0xd5, 0x42, 0x03, 0xe7, 0x8f, 0xcc, 0x91, 0xf3, 0x76, 0xe7, 0x08,
 		0x5a, 0xe9, 0x27, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xab, 0x59, 0x6f, 0xfb,
 		0x5a, 0xe9, 0x27, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xab, 0x59, 0x6f, 0xfb,
 		0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Google Go",
 		"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,
 		0xc4, 0xd9, 0x51, 0x29, 0x52, 0x96, 0x20, 0x55, 0xb3, 0x54, 0x7b, 0x4f,
 		0x6c, 0x82, 0x2e, 0x7d, 0x5c, 0x72, 0x5c, 0xc7, 0x47, 0x00, 0x00, 0x00,
 		0x6c, 0x82, 0x2e, 0x7d, 0x5c, 0x72, 0x5c, 0xc7, 0x47, 0x00, 0x00, 0x00,
 		0xff, 0xff, 0xe7, 0xd6, 0xf7, 0xa4, 0xbc, 0x00, 0x00, 0x00,
 		0xff, 0xff, 0xe7, 0xd6, 0xf7, 0xa4, 0xbc, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Java",
 		"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,
 		0xc9, 0x07, 0xae, 0xa1, 0xb9, 0x4c, 0x22, 0x3f, 0x5b, 0x4d, 0x65, 0x7b,
 		0x3d, 0x9f, 0x60, 0x5c, 0x71, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa9,
 		0x3d, 0x9f, 0x60, 0x5c, 0x71, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa9,
 		0x17, 0x4f, 0x2a, 0x18, 0x01, 0x00, 0x00,
 		0x17, 0x4f, 0x2a, 0x18, 0x01, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Objective-C",
 		"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,
 		0x78, 0xeb, 0xf6, 0x9c, 0x58, 0x85, 0x7f, 0x28, 0x58, 0x2b, 0xb6, 0xa6,
 		0x1c, 0xdd, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0xf0, 0xe2, 0xc0,
 		0x1c, 0xdd, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0xf0, 0xe2, 0xc0,
 		0x3a, 0x01, 0x00, 0x00,
 		0x3a, 0x01, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Python",
 		"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,
 		0x41, 0xb1, 0xbc, 0x23, 0x4d, 0xdf, 0x7d, 0xf0, 0x88, 0x6c, 0xbf, 0x3b,
 		0xf1, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xca, 0xf7, 0x91, 0x9e,
 		0xf1, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xca, 0xf7, 0x91, 0x9e,
 		0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00,
-		},
+	},
 		"conf/gitignore/Ruby",
 		"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,
 		0x42, 0xc2, 0x5f, 0x88, 0x57, 0x1b, 0xd8, 0x89, 0x3e, 0x15, 0x0e, 0xb8,
 		0x30, 0xdf, 0x60, 0x88, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xd2,
 		0x30, 0xdf, 0x60, 0x88, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xd2,
 		0xa8, 0x4c, 0xc3, 0x86, 0x00, 0x00,
 		0xa8, 0x4c, 0xc3, 0x86, 0x00, 0x00,
-		},
+	},
 		"conf/license/Affero GPL",
 		"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,
 		0x37, 0x23, 0x02, 0x0e, 0x94, 0x00, 0x65, 0xa1, 0x3f, 0x7d, 0xb3, 0x2f,
 		0x4c, 0x4b, 0x32, 0x5f, 0x77, 0xe7, 0xe7, 0x9a, 0xff, 0x6f, 0x00, 0x00,
 		0x4c, 0x4b, 0x32, 0x5f, 0x77, 0xe7, 0xe7, 0x9a, 0xff, 0x6f, 0x00, 0x00,
 		0x00, 0xff, 0xff, 0xa8, 0x76, 0x8d, 0x12, 0x3b, 0x2c, 0x00, 0x00,
 		0x00, 0xff, 0xff, 0xa8, 0x76, 0x8d, 0x12, 0x3b, 0x2c, 0x00, 0x00,
-		},
+	},
 		"conf/license/Apache v2 License",
 		"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,
 		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, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x26, 0x8b, 0xf2, 0xb7, 0x22, 0x00,
 		0x00,
 		0x00,
-		},
+	},
 		"conf/license/Artistic License 2.0",
 		"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,
 		0x95, 0xb8, 0xec, 0x49, 0x8a, 0xac, 0xd8, 0xd0, 0x39, 0xee, 0xdb, 0xbf,
 		0x02, 0x00, 0x00, 0xff, 0xff, 0x84, 0xcd, 0xba, 0x22, 0xc1, 0x05, 0x00,
 		0x02, 0x00, 0x00, 0xff, 0xff, 0x84, 0xcd, 0xba, 0x22, 0xc1, 0x05, 0x00,
 		0x00,
 		0x00,
-		},
+	},
 		"conf/license/BSD (3-Clause) License",
 		"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,
 		0x4d, 0xee, 0x25, 0x41, 0xb2, 0x70, 0x4f, 0xe6, 0xf0, 0xef, 0xb7, 0x30,
 		0xc7, 0xff, 0x7e, 0x8b, 0x83, 0x22, 0xe6, 0xff, 0x06, 0x00, 0x00, 0xff,
 		0xc7, 0xff, 0x7e, 0x8b, 0x83, 0x22, 0xe6, 0xff, 0x06, 0x00, 0x00, 0xff,
 		0xff, 0x82, 0x4d, 0xf9, 0x2b, 0x69, 0x46, 0x00, 0x00,
 		0xff, 0x82, 0x4d, 0xf9, 0x2b, 0x69, 0x46, 0x00, 0x00,
-		},
+	},
 		"conf/license/GPL v2",
 		"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,
 		0x42, 0x26, 0x78, 0x8e, 0x5c, 0x15, 0x81, 0x29, 0xe2, 0xdb, 0xf0, 0xd3,
 		0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0x86, 0xab, 0x31, 0x29, 0x04,
 		0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0x86, 0xab, 0x31, 0x29, 0x04,
 		0x00, 0x00,
 		0x00, 0x00,
-		},
+	},
 		"conf/license/MIT License",
 		"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,
 		0xa1, 0xe0, 0xec, 0xef, 0xe3, 0x03, 0xd2, 0x06, 0xe2, 0xc4, 0xa7, 0xa7,
 		0xe6, 0xa5, 0x16, 0x25, 0xe6, 0xc4, 0x27, 0x67, 0x5a, 0x73, 0x01, 0x02,
 		0xe6, 0xa5, 0x16, 0x25, 0xe6, 0xc4, 0x27, 0x67, 0x5a, 0x73, 0x01, 0x02,
 		0x00, 0x00, 0xff, 0xff, 0xcd, 0xf5, 0x53, 0x80, 0x6d, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0xff, 0xff, 0xcd, 0xf5, 0x53, 0x80, 0x6d, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/mysql.sql",
 		"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,
 		0xa1, 0xed, 0x82, 0x8e, 0x38, 0x6f, 0x11, 0x92, 0x13, 0x67, 0x75, 0xe7,
 		0xeb, 0xe5, 0xe4, 0x86, 0xef, 0xd7, 0xc1, 0x18, 0xfa, 0x04, 0x00, 0x00,
 		0xeb, 0xe5, 0xe4, 0x86, 0xef, 0xd7, 0xc1, 0x18, 0xfa, 0x04, 0x00, 0x00,
 		0xff, 0xff, 0x61, 0x60, 0x15, 0x6f, 0xc9, 0x00, 0x00, 0x00,
 		0xff, 0xff, 0x61, 0x60, 0x15, 0x6f, 0xc9, 0x00, 0x00, 0x00,
-		},
+	},
 		"conf/supervisor.ini",
 		"conf/supervisor.ini",
 	)
 	)
 }
 }
 
 
-
 // Asset loads and returns the asset for the given name.
 // Asset loads and returns the asset for the given name.
 // It returns an error if the asset could not be found or
 // It returns an error if the asset could not be found or
 // could not be loaded.
 // could not be loaded.
@@ -3584,7 +3599,7 @@ func AssetNames() []string {
 }
 }
 
 
 // _bindata is a table, holding each asset generator, mapped to its name.
 // _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/app.ini": conf_app_ini,
 	"conf/content/git-bare.zip": conf_content_git_bare_zip,
 	"conf/content/git-bare.zip": conf_content_git_bare_zip,
 	"conf/etc/supervisord.conf": conf_etc_supervisord_conf,
 	"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.
 // Copyright 2014 The Gogs Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
@@ -5,13 +6,208 @@
 package cron
 package cron
 
 
 import (
 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
 package log
 
 
 import (
 import (
+	"fmt"
 	"os"
 	"os"
+	"path"
 
 
 	"github.com/gogits/logs"
 	"github.com/gogits/logs"
 )
 )
 
 
 var (
 var (
-	loggers []*logs.BeeLogger
+	loggers   []*logs.BeeLogger
+	GitLogger *logs.BeeLogger
 )
 )
 
 
 func init() {
 func init() {
@@ -33,7 +36,15 @@ func NewLogger(bufLen int64, mode, config string) {
 		loggers = append(loggers, logger)
 		loggers = append(loggers, logger)
 	}
 	}
 	logger.SetLogFuncCallDepth(3)
 	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{}) {
 func Trace(format string, v ...interface{}) {

+ 44 - 35
modules/mailer/mail.go

@@ -17,6 +17,15 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 	"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
 // Create New mail message use MailFrom and MailUser
 func NewMailMessageFrom(To []string, from, subject, body string) Message {
 func NewMailMessageFrom(To []string, from, subject, body string) Message {
 	msg := NewHtmlMessage(To, from, subject, body)
 	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
 // Create New mail message use MailFrom and MailUser
 func NewMailMessage(To []string, subject, body string) Message {
 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 := make(map[interface{}]interface{}, 10)
 	data["AppName"] = setting.AppName
 	data["AppName"] = setting.AppName
 	data["AppVer"] = setting.AppVer
 	data["AppVer"] = setting.AppVer
@@ -37,84 +46,84 @@ func GetMailTmplData(user *models.User) map[interface{}]interface{} {
 	data["AppLogo"] = setting.AppLogo
 	data["AppLogo"] = setting.AppLogo
 	data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
 	data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
 	data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
 	data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
-	if user != nil {
-		data["User"] = user
+	if u != nil {
+		data["User"] = u
 	}
 	}
 	return data
 	return data
 }
 }
 
 
 // create a time limit code for user active
 // 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
 	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)
 	code := base.CreateTimeLimitCode(data, minutes, startInf)
 
 
 	// add tail hex username
 	// add tail hex username
-	code += hex.EncodeToString([]byte(user.LowerName))
+	code += hex.EncodeToString([]byte(u.LowerName))
 	return code
 	return code
 }
 }
 
 
 // Send user register mail with active 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"
 	subject := "Register success, Welcome"
 
 
-	data := GetMailTmplData(user)
+	data := GetMailTmplData(u)
 	data["Code"] = code
 	data["Code"] = code
-	body, err := r.HTMLString("mail/auth/register_success", data)
+	body, err := r.HTMLString(string(AUTH_REGISTER_SUCCESS), data)
 	if err != nil {
 	if err != nil {
 		log.Error("mail.SendRegisterMail(fail to render): %v", err)
 		log.Error("mail.SendRegisterMail(fail to render): %v", err)
 		return
 		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)
 	SendAsync(&msg)
 }
 }
 
 
 // Send email verify active email.
 // 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"
 	subject := "Verify your e-mail address"
 
 
-	data := GetMailTmplData(user)
+	data := GetMailTmplData(u)
 	data["Code"] = code
 	data["Code"] = code
-	body, err := r.HTMLString("mail/auth/active_email", data)
+	body, err := r.HTMLString(string(AUTH_ACTIVE), data)
 	if err != nil {
 	if err != nil {
 		log.Error("mail.SendActiveMail(fail to render): %v", err)
 		log.Error("mail.SendActiveMail(fail to render): %v", err)
 		return
 		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)
 	SendAsync(&msg)
 }
 }
 
 
 // Send reset password email.
 // 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"
 	subject := "Reset your password"
 
 
-	data := GetMailTmplData(user)
+	data := GetMailTmplData(u)
 	data["Code"] = code
 	data["Code"] = code
-	body, err := r.HTMLString("mail/auth/reset_passwd", data)
+	body, err := r.HTMLString(string(AUTH_RESET_PASSWORD), data)
 	if err != nil {
 	if err != nil {
 		log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
 		log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
 		return
 		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)
 	SendAsync(&msg)
 }
 }
 
 
 // SendIssueNotifyMail sends mail notification of all watchers of repository.
 // 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)
 	ws, err := models.GetWatchers(repo.Id)
 	if err != nil {
 	if err != nil {
 		return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error())
 		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))
 	tos := make([]string, 0, len(ws))
 	for i := range ws {
 	for i := range ws {
 		uid := ws[i].UserId
 		uid := ws[i].UserId
-		if user.Id == uid {
+		if u.Id == uid {
 			continue
 			continue
 		}
 		}
 		u, err := models.GetUserById(uid)
 		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>.",
 	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),
 		base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
 		setting.AppUrl, owner.Name, repo.Name, issue.Index)
 		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)
 	msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
 	SendAsync(&msg)
 	SendAsync(&msg)
 	return tos, nil
 	return tos, nil
 }
 }
 
 
 // SendIssueMentionMail sends mail notification for who are mentioned in issue.
 // 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 {
 	repo *models.Repository, issue *models.Issue, tos []string) error {
 
 
 	if len(tos) == 0 {
 	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["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
 	data["Subject"] = subject
 	data["Subject"] = subject
 
 
-	body, err := r.HTMLString("mail/notify/mention", data)
+	body, err := r.HTMLString(string(NOTIFY_MENTION), data)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("mail.SendIssueMentionMail(fail to render): %v", err)
 		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)
 	msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
 	SendAsync(&msg)
 	SendAsync(&msg)
 	return nil
 	return nil
 }
 }
 
 
 // SendCollaboratorMail sends mail notification to new collaborator.
 // 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 {
 	repo *models.Repository) error {
 
 
 	subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name)
 	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["RepoLink"] = path.Join(owner.Name, repo.Name)
 	data["Subject"] = subject
 	data["Subject"] = subject
 
 
-	body, err := r.HTMLString("mail/notify/collaborator", data)
+	body, err := r.HTMLString(string(NOTIFY_COLLABORATOR), data)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("mail.SendCollaboratorMail(fail to render): %v", err)
 		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)
 	SendAsync(&msg)
 	return nil
 	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.
 // 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.
 // 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 {
 	if form != nil {
 		auth.AssignForm(form, ctx.Data)
 		auth.AssignForm(form, ctx.Data)
 	}
 	}
@@ -133,7 +133,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
 	case 500:
 	case 500:
 		ctx.Data["Title"] = "Internal Server Error"
 		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{}) {
 func (ctx *Context) Debug(msg string, args ...interface{}) {
@@ -358,7 +358,7 @@ func InitContext() martini.Handler {
 		})
 		})
 
 
 		// Get user from session if logined.
 		// Get user from session if logined.
-		user := auth.SignedInUser(ctx.Session)
+		user := auth.SignedInUser(ctx.req.Header, ctx.Session)
 		ctx.User = user
 		ctx.User = user
 		ctx.IsSigned = user != nil
 		ctx.IsSigned = user != nil
 
 

+ 5 - 8
modules/middleware/repo.go

@@ -21,21 +21,17 @@ import (
 
 
 func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 	return func(ctx *Context, params martini.Params) {
 	return func(ctx *Context, params martini.Params) {
-		log.Trace(fmt.Sprint(args))
 		// valid brachname
 		// valid brachname
 		var validBranch bool
 		var validBranch bool
 		// display bare quick start if it is a bare repo
 		// display bare quick start if it is a bare repo
 		var displayBare bool
 		var displayBare bool
 
 
 		if len(args) >= 1 {
 		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 {
 		if len(args) >= 2 {
-			// displayBare = args[1]
-			displayBare = true
+			displayBare = args[1]
 		}
 		}
 
 
 		var (
 		var (
@@ -48,9 +44,10 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 		repoName := params["reponame"]
 		repoName := params["reponame"]
 		refName := params["branchname"]
 		refName := params["branchname"]
 
 
+		// TODO: need more advanced onwership and access level check.
 		// Collaborators who have write access can be seen as owners.
 		// Collaborators who have write access can be seen as owners.
 		if ctx.IsSigned {
 		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 {
 			if err != nil {
 				ctx.Handle(500, "RepoAssignment(HasAccess)", err)
 				ctx.Handle(500, "RepoAssignment(HasAccess)", err)
 				return
 				return
@@ -111,7 +108,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 				return
 				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 {
 			if err != nil {
 				ctx.Handle(500, "RepoAssignment(HasAccess)", err)
 				ctx.Handle(500, "RepoAssignment(HasAccess)", err)
 				return
 				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
 	StaticRootPath     string
 
 
 	// Security settings.
 	// 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.
 	// Repository settings.
 	RepoRootPath string
 	RepoRootPath string
@@ -86,8 +91,7 @@ var (
 	RunUser    string
 	RunUser    string
 )
 )
 
 
-// WorkDir returns absolute path of work directory.
-func WorkDir() (string, error) {
+func ExecPath() (string, error) {
 	file, err := exec.LookPath(os.Args[0])
 	file, err := exec.LookPath(os.Args[0])
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
@@ -96,7 +100,13 @@ func WorkDir() (string, error) {
 	if err != nil {
 	if err != nil {
 		return "", err
 		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.
 // NewConfigContext initializes configuration context.
@@ -154,6 +164,7 @@ func NewConfigContext() {
 	LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
 	LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
 	CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
 	CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
 	CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
 	CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
+	ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
 
 
 	RunUser = Cfg.MustValue("", "RUN_USER")
 	RunUser = Cfg.MustValue("", "RUN_USER")
 	curUser := os.Getenv("USER")
 	curUser := os.Getenv("USER")
@@ -171,6 +182,12 @@ func NewConfigContext() {
 		log.Fatal("Fail to get home directory: %v", err)
 		log.Fatal("Fail to get home directory: %v", err)
 	}
 	}
 	RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
 	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 {
 	if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
 		log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err)
 		log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err)
 	}
 	}
@@ -182,14 +199,15 @@ func NewConfigContext() {
 }
 }
 
 
 var Service struct {
 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() {
 func newService() {
@@ -198,6 +216,7 @@ func newService() {
 	Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
 	Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
 	Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
 	Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
 	Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
 	Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
+	Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION")
 }
 }
 
 
 var logLevels = map[string]string{
 var logLevels = map[string]string{
@@ -246,20 +265,20 @@ func newLogService() {
 				Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
 				Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
 				Cfg.MustInt(modeSec, "MAX_DAYS", 7))
 				Cfg.MustInt(modeSec, "MAX_DAYS", 7))
 		case "conn":
 		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_ON_MSG"),
 				Cfg.MustBool(modeSec, "RECONNECT"),
 				Cfg.MustBool(modeSec, "RECONNECT"),
 				Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
 				Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
 				Cfg.MustValue(modeSec, "ADDR", ":7020"))
 				Cfg.MustValue(modeSec, "ADDR", ":7020"))
 		case "smtp":
 		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, "USER", "example@example.com"),
 				Cfg.MustValue(modeSec, "PASSWD", "******"),
 				Cfg.MustValue(modeSec, "PASSWD", "******"),
 				Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
 				Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
 				Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
 				Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
 				Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
 				Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
 		case "database":
 		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, "DRIVER"),
 				Cfg.MustValue(modeSec, "CONN"))
 				Cfg.MustValue(modeSec, "CONN"))
 		}
 		}
@@ -330,6 +349,7 @@ func newSessionService() {
 type Mailer struct {
 type Mailer struct {
 	Name         string
 	Name         string
 	Host         string
 	Host         string
+	From         string
 	User, Passwd string
 	User, Passwd string
 }
 }
 
 
@@ -363,6 +383,7 @@ func newMailService() {
 		User:   Cfg.MustValue("mailer", "USER"),
 		User:   Cfg.MustValue("mailer", "USER"),
 		Passwd: Cfg.MustValue("mailer", "PASSWD"),
 		Passwd: Cfg.MustValue("mailer", "PASSWD"),
 	}
 	}
+	MailService.From = Cfg.MustValue("mailer", "FROM", MailService.User)
 	log.Info("Mail Service Enabled")
 	log.Info("Mail Service Enabled")
 }
 }
 
 
@@ -384,10 +405,15 @@ func newNotifyMailService() {
 		log.Warn("Notify Mail Service: Mail Service is not enabled")
 		log.Warn("Notify Mail Service: Mail Service is not enabled")
 		return
 		return
 	}
 	}
-	Service.NotifyMail = true
+	Service.EnableNotifyMail = true
 	log.Info("Notify Mail Service Enabled")
 	log.Info("Notify Mail Service Enabled")
 }
 }
 
 
+func newWebhookService() {
+	WebhookTaskInterval = Cfg.MustInt("webhook", "TASK_INTERVAL", 1)
+	WebhookDeliverTimeout = Cfg.MustInt("webhook", "DELIVER_TIMEOUT", 5)
+}
+
 func NewServices() {
 func NewServices() {
 	newService()
 	newService()
 	newLogService()
 	newLogService()
@@ -396,4 +422,5 @@ func NewServices() {
 	newMailService()
 	newMailService()
 	newRegisterMailService()
 	newRegisterMailService()
 	newNotifyMailService()
 	newNotifyMailService()
+	newWebhookService()
 }
 }

+ 5 - 5
modules/social/social.go

@@ -120,7 +120,7 @@ type SocialGithub struct {
 }
 }
 
 
 func (s *SocialGithub) Type() int {
 func (s *SocialGithub) Type() int {
-	return models.OT_GITHUB
+	return int(models.GITHUB)
 }
 }
 
 
 func newGitHubOauth(config *oauth.Config) {
 func newGitHubOauth(config *oauth.Config) {
@@ -174,7 +174,7 @@ type SocialGoogle struct {
 }
 }
 
 
 func (s *SocialGoogle) Type() int {
 func (s *SocialGoogle) Type() int {
-	return models.OT_GOOGLE
+	return int(models.GOOGLE)
 }
 }
 
 
 func newGoogleOauth(config *oauth.Config) {
 func newGoogleOauth(config *oauth.Config) {
@@ -229,7 +229,7 @@ type SocialTencent struct {
 }
 }
 
 
 func (s *SocialTencent) Type() int {
 func (s *SocialTencent) Type() int {
-	return models.OT_QQ
+	return int(models.QQ)
 }
 }
 
 
 func newTencentOauth(config *oauth.Config) {
 func newTencentOauth(config *oauth.Config) {
@@ -295,7 +295,7 @@ type SocialTwitter struct {
 }
 }
 
 
 func (s *SocialTwitter) Type() int {
 func (s *SocialTwitter) Type() int {
-	return models.OT_TWITTER
+	return int(models.TWITTER)
 }
 }
 
 
 func newTwitterOauth(config *oauth.Config) {
 func newTwitterOauth(config *oauth.Config) {
@@ -351,7 +351,7 @@ type SocialWeibo struct {
 }
 }
 
 
 func (s *SocialWeibo) Type() int {
 func (s *SocialWeibo) Type() int {
-	return models.OT_WEIBO
+	return int(models.WEIBO)
 }
 }
 
 
 func newWeiboOauth(config *oauth.Config) {
 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 {
 html, body {
     height: 100%;
     height: 100%;
-    font-family: Helvetica, Arial, sans-serif;
+    font-family: Arial, Helvetica, sans-serif;
 }
 }
 
 
 /* override bs3 */
 /* override bs3 */
@@ -257,6 +257,9 @@ html, body {
 
 
 .card .btn {
 .card .btn {
     cursor: pointer;
     cursor: pointer;
+}
+
+.card .btn-primary {
     margin-right: 1.2em;
     margin-right: 1.2em;
 }
 }
 
 
@@ -372,7 +375,7 @@ html, body {
 
 
 /* gogits repo create */
 /* gogits repo create */
 
 
-#repo-create {
+#repo-create, #org-create, #org-teams-create, #org-teams-edit {
     width: 800px;
     width: 800px;
 }
 }
 
 
@@ -638,6 +641,55 @@ html, body {
     margin: 0 .5em;
     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 */
 /* gogits repo single page */
 
 
 #body-nav.repo-nav {
 #body-nav.repo-nav {
@@ -1643,7 +1695,7 @@ html, body {
     vertical-align: top;
     vertical-align: top;
 }
 }
 
 
-#label-color-change-ipt2{
+#label-color-change-ipt2 {
     margin-top: 1px;
     margin-top: 1px;
 }
 }
 
 
@@ -1814,4 +1866,196 @@ html, body {
 
 
 #release-preview {
 #release-preview {
     margin: 6px 0;
     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 () {
     (function () {
         $('[data-ajax-name=release-preview]').on("click", function () {
         $('[data-ajax-name=release-preview]').on("click", function () {
             var $this = $(this);
             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 () {
             }, function () {
                 $($this.data("preview")).html("no content");
                 $($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 ($) {
     $(function () {
     $(function () {
         initCore();
         initCore();
@@ -780,6 +801,9 @@ function initRepoSetting() {
         if ($('#repo-setting-container').length) {
         if ($('#repo-setting-container').length) {
             initRepoSetting();
             initRepoSetting();
         }
         }
+        if ($('#repo-create').length) {
+            initRepoCreating();
+        }
     });
     });
 })(jQuery);
 })(jQuery);
 
 

+ 48 - 9
routers/admin/admin.go

@@ -14,10 +14,22 @@ import (
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/cron"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/middleware"
+	"github.com/gogits/gogs/modules/process"
 	"github.com/gogits/gogs/modules/setting"
 	"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 startTime = time.Now()
 
 
 var sysStatus struct {
 var sysStatus struct {
@@ -100,8 +112,11 @@ func updateSystemStatus() {
 }
 }
 
 
 // Operation types.
 // Operation types.
+type AdminOperation int
+
 const (
 const (
-	OT_CLEAN_OAUTH = iota + 1
+	CLEAN_UNBIND_OAUTH AdminOperation = iota + 1
+	CLEAN_INACTIVATE_USER
 )
 )
 
 
 func Dashboard(ctx *middleware.Context) {
 func Dashboard(ctx *middleware.Context) {
@@ -114,10 +129,13 @@ func Dashboard(ctx *middleware.Context) {
 		var err error
 		var err error
 		var success string
 		var success string
 
 
-		switch op {
-		case OT_CLEAN_OAUTH:
+		switch AdminOperation(op) {
+		case CLEAN_UNBIND_OAUTH:
 			success = "All unbind OAuthes have been deleted."
 			success = "All unbind OAuthes have been deleted."
 			err = models.CleanUnbindOauth()
 			err = models.CleanUnbindOauth()
+		case CLEAN_INACTIVATE_USER:
+			success = "All inactivate accounts have been deleted."
+			err = models.DeleteInactivateUsers()
 		}
 		}
 
 
 		if err != nil {
 		if err != nil {
@@ -132,7 +150,7 @@ func Dashboard(ctx *middleware.Context) {
 	ctx.Data["Stats"] = models.GetStatistic()
 	ctx.Data["Stats"] = models.GetStatistic()
 	updateSystemStatus()
 	updateSystemStatus()
 	ctx.Data["SysStatus"] = sysStatus
 	ctx.Data["SysStatus"] = sysStatus
-	ctx.HTML(200, "admin/dashboard")
+	ctx.HTML(200, DASHBOARD)
 }
 }
 
 
 func Users(ctx *middleware.Context) {
 func Users(ctx *middleware.Context) {
@@ -142,10 +160,10 @@ func Users(ctx *middleware.Context) {
 	var err error
 	var err error
 	ctx.Data["Users"], err = models.GetUsers(200, 0)
 	ctx.Data["Users"], err = models.GetUsers(200, 0)
 	if err != nil {
 	if err != nil {
-		ctx.Handle(500, "admin.Users", err)
+		ctx.Handle(500, "admin.Users(GetUsers)", err)
 		return
 		return
 	}
 	}
-	ctx.HTML(200, "admin/users")
+	ctx.HTML(200, USERS)
 }
 }
 
 
 func Repositories(ctx *middleware.Context) {
 func Repositories(ctx *middleware.Context) {
@@ -158,7 +176,7 @@ func Repositories(ctx *middleware.Context) {
 		ctx.Handle(500, "admin.Repositories", err)
 		ctx.Handle(500, "admin.Repositories", err)
 		return
 		return
 	}
 	}
-	ctx.HTML(200, "admin/repos")
+	ctx.HTML(200, REPOS)
 }
 }
 
 
 func Auths(ctx *middleware.Context) {
 func Auths(ctx *middleware.Context) {
@@ -171,7 +189,7 @@ func Auths(ctx *middleware.Context) {
 		ctx.Handle(500, "admin.Auths", err)
 		ctx.Handle(500, "admin.Auths", err)
 		return
 		return
 	}
 	}
-	ctx.HTML(200, "admin/auths")
+	ctx.HTML(200, AUTHS)
 }
 }
 
 
 func Config(ctx *middleware.Context) {
 func Config(ctx *middleware.Context) {
@@ -188,11 +206,15 @@ func Config(ctx *middleware.Context) {
 	ctx.Data["StaticRootPath"] = setting.StaticRootPath
 	ctx.Data["StaticRootPath"] = setting.StaticRootPath
 	ctx.Data["LogRootPath"] = setting.LogRootPath
 	ctx.Data["LogRootPath"] = setting.LogRootPath
 	ctx.Data["ScriptType"] = setting.ScriptType
 	ctx.Data["ScriptType"] = setting.ScriptType
+	ctx.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser
 
 
 	ctx.Data["Service"] = setting.Service
 	ctx.Data["Service"] = setting.Service
 
 
 	ctx.Data["DbCfg"] = models.DbCfg
 	ctx.Data["DbCfg"] = models.DbCfg
 
 
+	ctx.Data["WebhookTaskInterval"] = setting.WebhookTaskInterval
+	ctx.Data["WebhookDeliverTimeout"] = setting.WebhookDeliverTimeout
+
 	ctx.Data["MailerEnabled"] = false
 	ctx.Data["MailerEnabled"] = false
 	if setting.MailService != nil {
 	if setting.MailService != nil {
 		ctx.Data["MailerEnabled"] = true
 		ctx.Data["MailerEnabled"] = true
@@ -223,5 +245,22 @@ func Config(ctx *middleware.Context) {
 	}
 	}
 	ctx.Data["Loggers"] = loggers
 	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"
 	"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) {
 func NewAuthSource(ctx *middleware.Context) {
 	ctx.Data["Title"] = "New Authentication"
 	ctx.Data["Title"] = "New Authentication"
 	ctx.Data["PageIsAuths"] = true
 	ctx.Data["PageIsAuths"] = true
 	ctx.Data["LoginTypes"] = models.LoginTypes
 	ctx.Data["LoginTypes"] = models.LoginTypes
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
-	ctx.HTML(200, "admin/auths/new")
+	ctx.HTML(200, AUTH_NEW)
 }
 }
 
 
 func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 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
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "admin/auths/new")
+		ctx.HTML(200, AUTH_NEW)
 		return
 		return
 	}
 	}
 
 
 	var u core.Conversion
 	var u core.Conversion
-	switch form.Type {
-	case models.LT_LDAP:
+	switch models.LoginType(form.Type) {
+	case models.LDAP:
 		u = &models.LDAPConfig{
 		u = &models.LDAPConfig{
 			Ldapsource: ldap.Ldapsource{
 			Ldapsource: ldap.Ldapsource{
 				Host:         form.Host,
 				Host:         form.Host,
@@ -53,7 +58,7 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 				Name:         form.AuthName,
 				Name:         form.AuthName,
 			},
 			},
 		}
 		}
-	case models.LT_SMTP:
+	case models.SMTP:
 		u = &models.SMTPConfig{
 		u = &models.SMTPConfig{
 			Auth: form.SmtpAuth,
 			Auth: form.SmtpAuth,
 			Host: form.SmtpHost,
 			Host: form.SmtpHost,
@@ -66,15 +71,15 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 	}
 	}
 
 
 	var source = &models.LoginSource{
 	var source = &models.LoginSource{
-		Type:              form.Type,
+		Type:              models.LoginType(form.Type),
 		Name:              form.AuthName,
 		Name:              form.AuthName,
 		IsActived:         true,
 		IsActived:         true,
 		AllowAutoRegister: form.AllowAutoRegister,
 		AllowAutoRegister: form.AllowAutoRegister,
 		Cfg:               u,
 		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
 		return
 	}
 	}
 
 
@@ -97,11 +102,11 @@ func EditAuthSource(ctx *middleware.Context, params martini.Params) {
 	}
 	}
 	u, err := models.GetLoginSourceById(id)
 	u, err := models.GetLoginSourceById(id)
 	if err != nil {
 	if err != nil {
-		ctx.Handle(500, "admin.user.EditUser", err)
+		ctx.Handle(500, "admin.user.EditUser(GetLoginSourceById)", err)
 		return
 		return
 	}
 	}
 	ctx.Data["Source"] = u
 	ctx.Data["Source"] = u
-	ctx.HTML(200, "admin/auths/edit")
+	ctx.HTML(200, AUTH_EDIT)
 }
 }
 
 
 func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 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
 	ctx.Data["SMTPAuths"] = models.SMTPAuths
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "admin/auths/edit")
+		ctx.HTML(200, AUTH_EDIT)
 		return
 		return
 	}
 	}
 
 
 	var config core.Conversion
 	var config core.Conversion
-	switch form.Type {
-	case models.LT_LDAP:
+	switch models.LoginType(form.Type) {
+	case models.LDAP:
 		config = &models.LDAPConfig{
 		config = &models.LDAPConfig{
 			Ldapsource: ldap.Ldapsource{
 			Ldapsource: ldap.Ldapsource{
 				Host:         form.Host,
 				Host:         form.Host,
@@ -131,7 +136,7 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 				Name:         form.AuthName,
 				Name:         form.AuthName,
 			},
 			},
 		}
 		}
-	case models.LT_SMTP:
+	case models.SMTP:
 		config = &models.SMTPConfig{
 		config = &models.SMTPConfig{
 			Auth: form.SmtpAuth,
 			Auth: form.SmtpAuth,
 			Host: form.SmtpHost,
 			Host: form.SmtpHost,
@@ -147,13 +152,13 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 		Id:                form.Id,
 		Id:                form.Id,
 		Name:              form.AuthName,
 		Name:              form.AuthName,
 		IsActived:         form.IsActived,
 		IsActived:         form.IsActived,
-		Type:              form.Type,
+		Type:              models.LoginType(form.Type),
 		AllowAutoRegister: form.AllowAutoRegister,
 		AllowAutoRegister: form.AllowAutoRegister,
 		Cfg:               config,
 		Cfg:               config,
 	}
 	}
 
 
 	if err := models.UpdateSource(&u); err != nil {
 	if err := models.UpdateSource(&u); err != nil {
-		ctx.Handle(500, "admin.auths.EditAuth", err)
+		ctx.Handle(500, "admin.auths.EditAuth(UpdateSource)", err)
 		return
 		return
 	}
 	}
 
 
@@ -175,7 +180,7 @@ func DeleteAuthSource(ctx *middleware.Context, params martini.Params) {
 
 
 	a, err := models.GetLoginSourceById(id)
 	a, err := models.GetLoginSourceById(id)
 	if err != nil {
 	if err != nil {
-		ctx.Handle(500, "admin.auths.DeleteAuth", err)
+		ctx.Handle(500, "admin.auths.DeleteAuth(GetLoginSourceById)", err)
 		return
 		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.Flash.Error("This authentication still has used by some users, you should move them and then delete again.")
 			ctx.Redirect("/admin/auths/" + params["authid"])
 			ctx.Redirect("/admin/auths/" + params["authid"])
 		default:
 		default:
-			ctx.Handle(500, "admin.auths.DeleteAuth", err)
+			ctx.Handle(500, "admin.auths.DeleteAuth(DelLoginSource)", err)
 		}
 		}
 		return
 		return
 	}
 	}

+ 31 - 22
routers/admin/user.go

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

+ 7 - 2
routers/dashboard.go

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

+ 2 - 1
routers/dev/template.go

@@ -8,6 +8,7 @@ import (
 	"github.com/go-martini/martini"
 	"github.com/go-martini/martini"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/setting"
 	"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["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
 	ctx.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
 	ctx.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
 	ctx.Data["CurDbValue"] = ""
 	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/Unknwon/goconfig"
 	"github.com/go-martini/martini"
 	"github.com/go-martini/martini"
 	"github.com/go-xorm/xorm"
 	"github.com/go-xorm/xorm"
-	qlog "github.com/qiniu/log"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/auth"
@@ -27,6 +26,10 @@ import (
 	"github.com/gogits/gogs/modules/social"
 	"github.com/gogits/gogs/modules/social"
 )
 )
 
 
+const (
+	INSTALL base.TplName = "install"
+)
+
 func checkRunMode() {
 func checkRunMode() {
 	switch setting.Cfg.MustValue("", "RUN_MODE") {
 	switch setting.Cfg.MustValue("", "RUN_MODE") {
 	case "prod":
 	case "prod":
@@ -56,11 +59,12 @@ func GlobalInit() {
 
 
 	if setting.InstallLock {
 	if setting.InstallLock {
 		if err := models.NewEngine(); err != nil {
 		if err := models.NewEngine(); err != nil {
-			qlog.Fatal(err)
+			log.Fatal("Fail to initialize ORM engine: %v", err)
 		}
 		}
 
 
 		models.HasEngine = true
 		models.HasEngine = true
 		cron.NewCronContext()
 		cron.NewCronContext()
+		log.NewGitLogger(path.Join(setting.LogRootPath, "http.log"))
 	}
 	}
 	if models.EnableSQLite3 {
 	if models.EnableSQLite3 {
 		log.Info("SQLite3 Enabled")
 		log.Info("SQLite3 Enabled")
@@ -72,6 +76,7 @@ func renderDbOption(ctx *middleware.Context) {
 	ctx.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "SQLite3"}
 	ctx.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "SQLite3"}
 }
 }
 
 
+// @router /install [get]
 func Install(ctx *middleware.Context, form auth.InstallForm) {
 func Install(ctx *middleware.Context, form auth.InstallForm) {
 	if setting.InstallLock {
 	if setting.InstallLock {
 		ctx.Handle(404, "install.Install", errors.New("Installation is prohibited"))
 		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
 	ctx.Data["CurDbOption"] = curDbOp
 
 
 	auth.AssignForm(form, ctx.Data)
 	auth.AssignForm(form, ctx.Data)
-	ctx.HTML(200, "install")
+	ctx.HTML(200, INSTALL)
 }
 }
 
 
 func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	if setting.InstallLock {
 	if setting.InstallLock {
-		ctx.Handle(404, "install.Install", errors.New("Installation is prohibited"))
+		ctx.Handle(404, "install.InstallPost", errors.New("Installation is prohibited"))
 		return
 		return
 	}
 	}
 
 
@@ -135,12 +140,12 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	ctx.Data["CurDbOption"] = form.Database
 	ctx.Data["CurDbOption"] = form.Database
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "install")
+		ctx.HTML(200, INSTALL)
 		return
 		return
 	}
 	}
 
 
 	if _, err := exec.LookPath("git"); err != nil {
 	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
 		return
 	}
 	}
 
 
@@ -158,18 +163,19 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	// Set test engine.
 	// Set test engine.
 	var x *xorm.Engine
 	var x *xorm.Engine
 	if err := models.NewTestEngine(x); err != nil {
 	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`) {
 		if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
 			ctx.RenderWithErr("Your release version does not support SQLite3, please download the official binary version "+
 			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 {
 		} else {
-			ctx.RenderWithErr("Database setting is not correct: "+err.Error(), "install", &form)
+			ctx.RenderWithErr("Database setting is not correct: "+err.Error(), INSTALL, &form)
 		}
 		}
 		return
 		return
 	}
 	}
 
 
 	// Test repository root path.
 	// Test repository root path.
 	if err := os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil {
 	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
 		return
 	}
 	}
 
 
@@ -180,7 +186,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	}
 	}
 	// Does not check run user when the install lock is off.
 	// Does not check run user when the install lock is off.
 	if form.RunUser != curUser {
 	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
 		return
 	}
 	}
 
 
@@ -214,18 +220,18 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 
 
 	os.MkdirAll("custom/conf", os.ModePerm)
 	os.MkdirAll("custom/conf", os.ModePerm)
 	if err := goconfig.SaveConfigFile(setting.Cfg, path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
 	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
 		return
 	}
 	}
 
 
 	GlobalInit()
 	GlobalInit()
 
 
 	// Create admin account.
 	// 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 {
 		IsAdmin: true, IsActive: true}); err != nil {
 		if err != models.ErrUserAlreadyExist {
 		if err != models.ErrUserAlreadyExist {
 			setting.InstallLock = false
 			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
 			return
 		}
 		}
 		log.Info("Admin account already exist")
 		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 (
 import (
 	"github.com/go-martini/martini"
 	"github.com/go-martini/martini"
 
 
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/middleware"
 )
 )
 
 
+const (
+	BRANCH base.TplName = "repo/branch"
+)
+
 func Branches(ctx *middleware.Context, params martini.Params) {
 func Branches(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["Title"] = "Branches"
 	ctx.Data["Title"] = "Branches"
 	ctx.Data["IsRepoToolbarBranches"] = true
 	ctx.Data["IsRepoToolbarBranches"] = true
 
 
 	brs, err := ctx.Repo.GitRepo.GetBranches()
 	brs, err := ctx.Repo.GitRepo.GetBranches()
 	if err != nil {
 	if err != nil {
-		ctx.Handle(500, "repo.Branches", err)
+		ctx.Handle(500, "repo.Branches(GetBranches)", err)
 		return
 		return
 	} else if len(brs) == 0 {
 	} else if len(brs) == 0 {
-		ctx.Handle(404, "repo.Branches", nil)
+		ctx.Handle(404, "repo.Branches(GetBranches)", nil)
 		return
 		return
 	}
 	}
 
 
 	ctx.Data["Branches"] = brs
 	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"
 	"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) {
 func Commits(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["IsRepoToolbarCommits"] = true
 	ctx.Data["IsRepoToolbarCommits"] = true
 
 
@@ -22,10 +27,10 @@ func Commits(ctx *middleware.Context, params martini.Params) {
 
 
 	brs, err := ctx.Repo.GitRepo.GetBranches()
 	brs, err := ctx.Repo.GitRepo.GetBranches()
 	if err != nil {
 	if err != nil {
-		ctx.Handle(500, "repo.Commits", err)
+		ctx.Handle(500, "repo.Commits(GetBranches)", err)
 		return
 		return
 	} else if len(brs) == 0 {
 	} else if len(brs) == 0 {
-		ctx.Handle(404, "repo.Commits", nil)
+		ctx.Handle(404, "repo.Commits(GetBranches)", nil)
 		return
 		return
 	}
 	}
 
 
@@ -61,7 +66,43 @@ func Commits(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["LastPageNum"] = lastPage
 	ctx.Data["LastPageNum"] = lastPage
 	ctx.Data["NextPageNum"] = nextPage
 	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) {
 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)
 	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
 	if err != nil {
 	if err != nil {
-		ctx.Handle(404, "repo.Diff", err)
+		ctx.Handle(404, "repo.Diff(GetDiff)", err)
 		return
 		return
 	}
 	}
 
 
@@ -119,43 +160,7 @@ func Diff(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
 	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
 	ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId)
 	ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId)
 	ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", 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) {
 func FileHistory(ctx *middleware.Context, params martini.Params) {
@@ -184,8 +189,7 @@ func FileHistory(ctx *middleware.Context, params martini.Params) {
 	if err != nil {
 	if err != nil {
 		ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
 		ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
 		return
 		return
-	}
-	if commitsCount == 0 {
+	} else if commitsCount == 0 {
 		ctx.Handle(404, "repo.FileHistory", nil)
 		ctx.Handle(404, "repo.FileHistory", nil)
 		return
 		return
 	}
 	}
@@ -217,5 +221,5 @@ func FileHistory(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["LastPageNum"] = lastPage
 	ctx.Data["LastPageNum"] = lastPage
 	ctx.Data["NextPageNum"] = nextPage
 	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 (
 import (
 	"bytes"
 	"bytes"
 	"fmt"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"io/ioutil"
-	"log"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
@@ -22,6 +20,7 @@ import (
 
 
 	"github.com/go-martini/martini"
 	"github.com/go-martini/martini"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/setting"
 	"github.com/gogits/gogs/modules/setting"
 )
 )
@@ -107,9 +106,9 @@ func Http(ctx *middleware.Context, params martini.Params) {
 		}
 		}
 
 
 		if !isPublicPull {
 		if !isPublicPull {
-			var tp = models.AU_WRITABLE
+			var tp = models.WRITABLE
 			if isPull {
 			if isPull {
-				tp = models.AU_READABLE
+				tp = models.READABLE
 			}
 			}
 
 
 			has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
 			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)
 				ctx.Handle(401, "no basic auth and digit auth", nil)
 				return
 				return
 			} else if !has {
 			} 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 {
 					if err != nil || !has {
 						ctx.Handle(401, "no basic auth and digit auth", nil)
 						ctx.Handle(401, "no basic auth and digit auth", nil)
 						return
 						return
@@ -141,7 +140,10 @@ func Http(ctx *middleware.Context, params martini.Params) {
 					newCommitId := fields[1]
 					newCommitId := fields[1]
 					refName := fields[2]
 					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
 // Request handling function
 func HttpBackend(config *Config) http.HandlerFunc {
 func HttpBackend(config *Config) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 	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 {
 		for _, route := range routes {
 			if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
 			if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
 				if route.method != r.Method {
 				if route.method != r.Method {
@@ -202,7 +203,7 @@ func HttpBackend(config *Config) http.HandlerFunc {
 				dir, err := getGitDir(config, m[1])
 				dir, err := getGitDir(config, m[1])
 
 
 				if err != nil {
 				if err != nil {
-					log.Print(err)
+					log.GitLogger.Error(err.Error())
 					renderNotFound(w)
 					renderNotFound(w)
 					return
 					return
 				}
 				}
@@ -212,13 +213,13 @@ func HttpBackend(config *Config) http.HandlerFunc {
 				return
 				return
 			}
 			}
 		}
 		}
+
 		renderNotFound(w)
 		renderNotFound(w)
 		return
 		return
 	}
 	}
 }
 }
 
 
 // Actual command handling functions
 // Actual command handling functions
-
 func serviceUploadPack(hr handler) {
 func serviceUploadPack(hr handler) {
 	serviceRpc("upload-pack", hr)
 	serviceRpc("upload-pack", hr)
 }
 }
@@ -236,36 +237,24 @@ func serviceRpc(rpc string, hr handler) {
 		return
 		return
 	}
 	}
 
 
-	input, _ := ioutil.ReadAll(r.Body)
-
 	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
 	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 
 
+	input, _ := ioutil.ReadAll(r.Body)
+	br := bytes.NewReader(input)
+
 	args := []string{rpc, "--stateless-rpc", dir}
 	args := []string{rpc, "--stateless-rpc", dir}
 	cmd := exec.Command(hr.Config.GitBinPath, args...)
 	cmd := exec.Command(hr.Config.GitBinPath, args...)
 	cmd.Dir = dir
 	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 {
 	if err != nil {
-		log.Print(err)
+		log.GitLogger.Error(err.Error())
 		return
 		return
 	}
 	}
 
 
-	in.Write(input)
-	io.Copy(w, stdout)
-	cmd.Wait()
-
 	if hr.Config.OnSucceed != nil {
 	if hr.Config.OnSucceed != nil {
 		hr.Config.OnSucceed(rpc, input)
 		hr.Config.OnSucceed(rpc, input)
 	}
 	}
@@ -345,7 +334,7 @@ func getGitDir(config *Config, fPath string) (string, error) {
 		cwd, err := os.Getwd()
 		cwd, err := os.Getwd()
 
 
 		if err != nil {
 		if err != nil {
-			log.Print(err)
+			log.GitLogger.Error(err.Error())
 			return "", err
 			return "", err
 		}
 		}
 
 
@@ -422,7 +411,7 @@ func gitCommand(gitBinPath, dir string, args ...string) []byte {
 	out, err := command.Output()
 	out, err := command.Output()
 
 
 	if err != nil {
 	if err != nil {
-		log.Print(err)
+		log.GitLogger.Error(err.Error())
 	}
 	}
 
 
 	return out
 	return out

+ 29 - 9
routers/repo/issue.go

@@ -22,6 +22,16 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 	"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) {
 func Issues(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Issues"
 	ctx.Data["Title"] = "Issues"
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssues"] = true
@@ -134,7 +144,7 @@ func Issues(ctx *middleware.Context) {
 	} else {
 	} else {
 		ctx.Data["ShowCount"] = issueStats.OpenCount
 		ctx.Data["ShowCount"] = issueStats.OpenCount
 	}
 	}
-	ctx.HTML(200, "issue/list")
+	ctx.HTML(200, ISSUES)
 }
 }
 
 
 func CreateIssue(ctx *middleware.Context, params martini.Params) {
 func CreateIssue(ctx *middleware.Context, params martini.Params) {
@@ -161,7 +171,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params) {
 		return
 		return
 	}
 	}
 	ctx.Data["Collaborators"] = us
 	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) {
 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
 	ctx.Data["Collaborators"] = us
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "issue/create")
+		ctx.HTML(200, ISSUE_CREATE)
 		return
 		return
 	}
 	}
 
 
@@ -250,7 +260,7 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
 	}
 	}
 
 
 	// Mail watchers and mentions.
 	// 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)
 		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
 		if err != nil {
 		if err != nil {
 			ctx.Handle(500, "issue.CreateIssue(SendIssueNotifyMail)", err)
 			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["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = false
 	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) {
 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.
 	// Mail watchers and mentions.
-	if setting.Service.NotifyMail {
+	if setting.Service.EnableNotifyMail {
 		issue.Content = content
 		issue.Content = content
 		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
 		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
 		if err != nil {
 		if err != nil {
@@ -794,14 +804,14 @@ func Milestones(ctx *middleware.Context) {
 	} else {
 	} else {
 		ctx.Data["State"] = "open"
 		ctx.Data["State"] = "open"
 	}
 	}
-	ctx.HTML(200, "issue/milestone")
+	ctx.HTML(200, MILESTONE)
 }
 }
 
 
 func NewMilestone(ctx *middleware.Context) {
 func NewMilestone(ctx *middleware.Context) {
 	ctx.Data["Title"] = "New Milestone"
 	ctx.Data["Title"] = "New Milestone"
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
-	ctx.HTML(200, "issue/milestone_new")
+	ctx.HTML(200, MILESTONE_NEW)
 }
 }
 
 
 func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
 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["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
 
 
+	if ctx.HasError() {
+		ctx.HTML(200, MILESTONE_NEW)
+		return
+	}
+
 	var deadline time.Time
 	var deadline time.Time
 	var err error
 	var err error
 	if len(form.Deadline) == 0 {
 	if len(form.Deadline) == 0 {
@@ -890,7 +905,7 @@ func UpdateMilestone(ctx *middleware.Context, params martini.Params) {
 	}
 	}
 	ctx.Data["Milestone"] = mile
 	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) {
 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
 		return
 	}
 	}
 
 
+	if ctx.HasError() {
+		ctx.HTML(200, MILESTONE_EDIT)
+		return
+	}
+
 	var deadline time.Time
 	var deadline time.Time
 	if len(form.Deadline) == 0 {
 	if len(form.Deadline) == 0 {
 		form.Deadline = "12/31/9999"
 		form.Deadline = "12/31/9999"

+ 6 - 1
routers/repo/pull.go

@@ -7,10 +7,15 @@ package repo
 import (
 import (
 	"github.com/go-martini/martini"
 	"github.com/go-martini/martini"
 
 
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/middleware"
 )
 )
 
 
+const (
+	PULLS base.TplName = "repo/pulls"
+)
+
 func Pulls(ctx *middleware.Context, params martini.Params) {
 func Pulls(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["IsRepoToolbarPulls"] = true
 	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
 package repo
 
 
 import (
 import (
-	"sort"
+	"github.com/go-martini/martini"
 
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/auth"
@@ -14,21 +14,11 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 	"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) {
 func Releases(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Releases"
 	ctx.Data["Title"] = "Releases"
@@ -52,65 +42,88 @@ func Releases(ctx *middleware.Context) {
 		return
 		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 i, rawTag := range rawTags {
 		for _, rel := range rels {
 		for _, rel := range rels {
+			if rel.IsDraft && !ctx.Repo.IsOwner {
+				continue
+			}
 			if rel.TagName == rawTag {
 			if rel.TagName == rawTag {
 				rel.Publisher, err = models.GetUserById(rel.PublisherId)
 				rel.Publisher, err = models.GetUserById(rel.PublisherId)
 				if err != nil {
 				if err != nil {
 					ctx.Handle(500, "release.Releases(GetUserById)", err)
 					ctx.Handle(500, "release.Releases(GetUserById)", err)
 					return
 					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)
 				rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink)
-				tags.rels[i] = rel
+				tags[i] = rel
 				break
 				break
 			}
 			}
 		}
 		}
 
 
-		if tags.rels[i] == nil {
+		if tags[i] == nil {
 			commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag)
 			commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag)
 			if err != nil {
 			if err != nil {
-				ctx.Handle(500, "release.Releases(GetCommitOfTag)", err)
+				ctx.Handle(500, "release.Releases(GetCommitOfTag2)", err)
 				return
 				return
 			}
 			}
 
 
-			tags.rels[i] = &models.Release{
+			tags[i] = &models.Release{
 				Title:   rawTag,
 				Title:   rawTag,
 				TagName: 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 {
 			if err != nil {
 				ctx.Handle(500, "release.Releases(CommitsCount)", err)
 				ctx.Handle(500, "release.Releases(CommitsCount)", err)
 				return
 				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 {
 	if !ctx.Repo.IsOwner {
-		ctx.Handle(404, "release.ReleasesNew", nil)
+		ctx.Handle(403, "release.ReleasesNew", nil)
 		return
 		return
 	}
 	}
 
 
 	ctx.Data["Title"] = "New Release"
 	ctx.Data["Title"] = "New Release"
 	ctx.Data["IsRepoToolbarReleases"] = true
 	ctx.Data["IsRepoToolbarReleases"] = true
 	ctx.Data["IsRepoReleaseNew"] = 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 {
 	if !ctx.Repo.IsOwner {
-		ctx.Handle(404, "release.ReleasesNew", nil)
+		ctx.Handle(403, "release.ReleasesNew", nil)
 		return
 		return
 	}
 	}
 
 
@@ -119,7 +132,7 @@ func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
 	ctx.Data["IsRepoReleaseNew"] = true
 	ctx.Data["IsRepoReleaseNew"] = true
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "release/new")
+		ctx.HTML(200, RELEASE_NEW)
 		return
 		return
 	}
 	}
 
 
@@ -129,14 +142,21 @@ func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
 		return
 		return
 	}
 	}
 
 
+	if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
+		ctx.RenderWithErr("Target branch does not exist", "release/new", &form)
+		return
+	}
+
 	rel := &models.Release{
 	rel := &models.Release{
 		RepoId:       ctx.Repo.Repository.Id,
 		RepoId:       ctx.Repo.Repository.Id,
 		PublisherId:  ctx.User.Id,
 		PublisherId:  ctx.User.Id,
 		Title:        form.Title,
 		Title:        form.Title,
 		TagName:      form.TagName,
 		TagName:      form.TagName,
-		SHA1:         ctx.Repo.Commit.Id.String(),
+		Target:       form.Target,
+		Sha1:         ctx.Repo.Commit.Id.String(),
 		NumCommits:   commitsCount,
 		NumCommits:   commitsCount,
 		Note:         form.Content,
 		Note:         form.Content,
+		IsDraft:      len(form.Draft) > 0,
 		IsPrerelease: form.Prerelease,
 		IsPrerelease: form.Prerelease,
 	}
 	}
 
 
@@ -152,3 +172,63 @@ func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
 
 
 	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
 	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"
 	"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) {
 func Create(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Create repository"
 	ctx.Data["Title"] = "Create repository"
 	ctx.Data["PageIsNewRepo"] = true
 	ctx.Data["PageIsNewRepo"] = true
 	ctx.Data["LanguageIgns"] = models.LanguageIgns
 	ctx.Data["LanguageIgns"] = models.LanguageIgns
 	ctx.Data["Licenses"] = models.Licenses
 	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) {
 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["LanguageIgns"] = models.LanguageIgns
 	ctx.Data["Licenses"] = models.Licenses
 	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() {
 	if ctx.HasError() {
-		ctx.HTML(200, "repo/create")
+		ctx.HTML(200, CREATE)
 		return
 		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)
 		form.Language, form.License, form.Private, false, form.InitReadme)
 	if err == nil {
 	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
 		return
 	} else if err == models.ErrRepoAlreadyExist {
 	} 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
 		return
 	} else if err == models.ErrRepoNameIllegal {
 	} else if err == models.ErrRepoNameIllegal {
-		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form)
+		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), CREATE, &form)
 		return
 		return
 	}
 	}
 
 
 	if repo != nil {
 	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) {
 func Migrate(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Migrate repository"
 	ctx.Data["Title"] = "Migrate repository"
 	ctx.Data["PageIsNewRepo"] = true
 	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) {
 func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
 	ctx.Data["Title"] = "Migrate repository"
 	ctx.Data["Title"] = "Migrate repository"
 	ctx.Data["PageIsNewRepo"] = true
 	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() {
 	if ctx.HasError() {
-		ctx.HTML(200, "repo/migrate")
+		ctx.HTML(200, MIGRATE)
 		return
 		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",
 	authStr := strings.Replace(fmt.Sprintf("://%s:%s",
 		form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
 		form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
 	url := strings.Replace(form.Url, "://", authStr+"@", 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)
 		form.Mirror, url)
 	if err == nil {
 	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
 		return
 	} else if err == models.ErrRepoAlreadyExist {
 	} 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
 		return
 	} else if err == models.ErrRepoNameIllegal {
 	} else if err == models.ErrRepoNameIllegal {
-		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/migrate", &form)
+		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), MIGRATE, &form)
 		return
 		return
 	}
 	}
 
 
 	if repo != nil {
 	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)
 			log.Error("repo.MigratePost(DeleteRepository): %v", errDelete)
 		}
 		}
 	}
 	}
 
 
 	if strings.Contains(err.Error(), "Authentication failed") {
 	if strings.Contains(err.Error(), "Authentication failed") {
-		ctx.RenderWithErr(err.Error(), "repo/migrate", &form)
+		ctx.RenderWithErr(err.Error(), MIGRATE, &form)
 		return
 		return
 	}
 	}
-	ctx.Handle(500, "repo.Migrate", err)
+	ctx.Handle(500, "repo.Migrate(MigrateRepository)", err)
 }
 }
 
 
 func Single(ctx *middleware.Context, params martini.Params) {
 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["Treenames"] = treenames
 	ctx.Data["TreePath"] = treePath
 	ctx.Data["TreePath"] = treePath
 	ctx.Data["BranchLink"] = branchLink
 	ctx.Data["BranchLink"] = branchLink
-	ctx.HTML(200, "repo/single")
+	ctx.HTML(200, SINGLE)
 }
 }
 
 
 func basicEncode(username, password string) string {
 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) {
 func authRequired(ctx *middleware.Context) {
 	ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
 	ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
 	ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
 	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) {
 func Action(ctx *middleware.Context, params martini.Params) {

+ 68 - 41
routers/repo/setting.go

@@ -7,6 +7,7 @@ package repo
 import (
 import (
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
+	"time"
 
 
 	"github.com/go-martini/martini"
 	"github.com/go-martini/martini"
 
 
@@ -19,10 +20,19 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 	"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) {
 func Setting(ctx *middleware.Context) {
 	ctx.Data["IsRepoToolbarSetting"] = true
 	ctx.Data["IsRepoToolbarSetting"] = true
 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings"
 	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) {
 func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
@@ -31,7 +41,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 	switch ctx.Query("action") {
 	switch ctx.Query("action") {
 	case "update":
 	case "update":
 		if ctx.HasError() {
 		if ctx.HasError() {
-			ctx.HTML(200, "repo/setting")
+			ctx.HTML(200, SETTING)
 			return
 			return
 		}
 		}
 
 
@@ -43,7 +53,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 				ctx.Handle(500, "setting.SettingPost(update: check existence)", err)
 				ctx.Handle(500, "setting.SettingPost(update: check existence)", err)
 				return
 				return
 			} else if isExist {
 			} 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
 				return
 			} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
 			} 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)
 				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 ctx.Repo.Repository.IsMirror {
 			if form.Interval > 0 {
 			if form.Interval > 0 {
 				ctx.Repo.Mirror.Interval = form.Interval
 				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 {
 				if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
 					log.Error("setting.SettingPost(UpdateMirror): %v", err)
 					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))
 		ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name))
 	case "transfer":
 	case "transfer":
 		if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
 		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
 			return
 		} else if ctx.Repo.Repository.IsMirror {
 		} else if ctx.Repo.Repository.IsMirror {
 			ctx.Error(404)
 			ctx.Error(404)
@@ -96,7 +107,7 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 			ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err)
 			ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err)
 			return
 			return
 		} else if !isExist {
 		} 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
 			return
 		} else if err = models.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository); err != nil {
 		} else if err = models.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository); err != nil {
 			ctx.Handle(500, "setting.SettingPost(transfer repository)", err)
 			ctx.Handle(500, "setting.SettingPost(transfer repository)", err)
@@ -107,17 +118,27 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 		ctx.Redirect("/")
 		ctx.Redirect("/")
 	case "delete":
 	case "delete":
 		if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
 		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
 			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
 			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.Data["Collaborators"] = us
-	ctx.HTML(200, "repo/collaboration")
+	ctx.HTML(200, COLLABORATION)
 }
 }
 
 
 func CollaborationPost(ctx *middleware.Context) {
 func CollaborationPost(ctx *middleware.Context) {
@@ -164,7 +185,7 @@ func CollaborationPost(ctx *middleware.Context) {
 		ctx.Redirect(ctx.Req.RequestURI)
 		ctx.Redirect(ctx.Req.RequestURI)
 		return
 		return
 	}
 	}
-	has, err := models.HasAccess(name, repoLink, models.AU_WRITABLE)
+	has, err := models.HasAccess(name, repoLink, models.WRITABLE)
 	if err != nil {
 	if err != nil {
 		ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err)
 		ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err)
 		return
 		return
@@ -185,12 +206,12 @@ func CollaborationPost(ctx *middleware.Context) {
 	}
 	}
 
 
 	if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink,
 	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)
 		ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err)
 		return
 		return
 	}
 	}
 
 
-	if setting.Service.NotifyMail {
+	if setting.Service.EnableNotifyMail {
 		if err = mailer.SendCollaboratorMail(ctx.Render, u, ctx.User, ctx.Repo.Repository); err != nil {
 		if err = mailer.SendCollaboratorMail(ctx.Render, u, ctx.User, ctx.Repo.Repository); err != nil {
 			ctx.Handle(500, "setting.CollaborationPost(SendCollaboratorMail)", err)
 			ctx.Handle(500, "setting.CollaborationPost(SendCollaboratorMail)", err)
 			return
 			return
@@ -224,13 +245,13 @@ func WebHooks(ctx *middleware.Context) {
 	}
 	}
 
 
 	ctx.Data["Webhooks"] = ws
 	ctx.Data["Webhooks"] = ws
-	ctx.HTML(200, "repo/hooks")
+	ctx.HTML(200, HOOKS)
 }
 }
 
 
 func WebHooksAdd(ctx *middleware.Context) {
 func WebHooksAdd(ctx *middleware.Context) {
 	ctx.Data["IsRepoToolbarWebHooks"] = true
 	ctx.Data["IsRepoToolbarWebHooks"] = true
 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
 	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) {
 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"
 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "repo/hooks_add")
+		ctx.HTML(200, HOOK_ADD)
 		return
 		return
 	}
 	}
 
 
-	ct := models.CT_JSON
+	ct := models.JSON
 	if form.ContentType == "2" {
 	if form.ContentType == "2" {
-		ct = models.CT_FORM
+		ct = models.FORM
 	}
 	}
 
 
 	w := &models.Webhook{
 	w := &models.Webhook{
@@ -257,8 +278,8 @@ func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 		},
 		},
 		IsActive: form.Active,
 		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
 		return
 	} else if err := models.CreateWebhook(w); err != nil {
 	} else if err := models.CreateWebhook(w); err != nil {
 		ctx.Handle(500, "setting.WebHooksAddPost(CreateWebhook)", err)
 		ctx.Handle(500, "setting.WebHooksAddPost(CreateWebhook)", err)
@@ -291,42 +312,48 @@ func WebHooksEdit(ctx *middleware.Context, params martini.Params) {
 
 
 	w.GetEvent()
 	w.GetEvent()
 	ctx.Data["Webhook"] = w
 	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) {
 func WebHooksEditPost(ctx *middleware.Context, params martini.Params, form auth.NewWebhookForm) {
 	ctx.Data["IsRepoToolbarWebHooks"] = true
 	ctx.Data["IsRepoToolbarWebHooks"] = true
 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook"
 	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()
 	hookId, _ := base.StrTo(params["id"]).Int64()
 	if hookId == 0 {
 	if hookId == 0 {
 		ctx.Handle(404, "setting.WebHooksEditPost", nil)
 		ctx.Handle(404, "setting.WebHooksEditPost", nil)
 		return
 		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" {
 	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
 		return
 	} else if err := models.UpdateWebhook(w); err != nil {
 	} else if err := models.UpdateWebhook(w); err != nil {
 		ctx.Handle(500, "setting.WebHooksEditPost(WebHooksEditPost)", err)
 		ctx.Handle(500, "setting.WebHooksEditPost(WebHooksEditPost)", err)

+ 30 - 11
routers/user/home.go

@@ -17,10 +17,25 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 	"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) {
 func Dashboard(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Dashboard"
 	ctx.Data["Title"] = "Dashboard"
 	ctx.Data["PageIsUserDashboard"] = true
 	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
 	var err error
 	ctx.Data["MyRepos"], err = models.GetRepositories(ctx.User.Id, true)
 	ctx.Data["MyRepos"], err = models.GetRepositories(ctx.User.Id, true)
 	if err != nil {
 	if err != nil {
@@ -45,21 +60,21 @@ func Dashboard(ctx *middleware.Context) {
 	for _, act := range actions {
 	for _, act := range actions {
 		if act.IsPrivate {
 		if act.IsPrivate {
 			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
 			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
-				models.AU_READABLE); !has {
+				models.READABLE); !has {
 				continue
 				continue
 			}
 			}
 		}
 		}
 		feeds = append(feeds, act)
 		feeds = append(feeds, act)
 	}
 	}
 	ctx.Data["Feeds"] = feeds
 	ctx.Data["Feeds"] = feeds
-	ctx.HTML(200, "user/dashboard")
+	ctx.HTML(200, DASHBOARD)
 }
 }
 
 
 func Profile(ctx *middleware.Context, params martini.Params) {
 func Profile(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["Title"] = "Profile"
 	ctx.Data["Title"] = "Profile"
 	ctx.Data["PageIsUserProfile"] = true
 	ctx.Data["PageIsUserProfile"] = true
 
 
-	user, err := models.GetUserByName(params["username"])
+	u, err := models.GetUserByName(params["username"])
 	if err != nil {
 	if err != nil {
 		if err == models.ErrUserNotExist {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "user.Profile(GetUserByName)", err)
 			ctx.Handle(404, "user.Profile(GetUserByName)", err)
@@ -68,26 +83,30 @@ func Profile(ctx *middleware.Context, params martini.Params) {
 		}
 		}
 		return
 		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")
 	tab := ctx.Query("tab")
 	ctx.Data["TabName"] = tab
 	ctx.Data["TabName"] = tab
 	switch tab {
 	switch tab {
 	case "activity":
 	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 {
 		if err != nil {
 			ctx.Handle(500, "user.Profile(GetFeeds)", err)
 			ctx.Handle(500, "user.Profile(GetFeeds)", err)
 			return
 			return
 		}
 		}
 	default:
 	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 {
 		if err != nil {
 			ctx.Handle(500, "user.Profile(GetRepositories)", err)
 			ctx.Handle(500, "user.Profile(GetRepositories)", err)
 			return
 			return
 		}
 		}
 	}
 	}
 
 
-	ctx.HTML(200, "user/profile")
+	ctx.HTML(200, PROFILE)
 }
 }
 
 
 func Email2User(ctx *middleware.Context) {
 func Email2User(ctx *middleware.Context) {
@@ -119,7 +138,7 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
 	for _, act := range actions {
 	for _, act := range actions {
 		if act.IsPrivate {
 		if act.IsPrivate {
 			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
 			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
-				models.AU_READABLE); !has {
+				models.READABLE); !has {
 				continue
 				continue
 			}
 			}
 		}
 		}
@@ -254,13 +273,13 @@ func Issues(ctx *middleware.Context) {
 	} else {
 	} else {
 		ctx.Data["ShowCount"] = issueStats.OpenCount
 		ctx.Data["ShowCount"] = issueStats.OpenCount
 	}
 	}
-	ctx.HTML(200, "user/issue")
+	ctx.HTML(200, ISSUES)
 }
 }
 
 
 func Pulls(ctx *middleware.Context) {
 func Pulls(ctx *middleware.Context) {
-	ctx.HTML(200, "user/pulls")
+	ctx.HTML(200, PULLS)
 }
 }
 
 
 func Stars(ctx *middleware.Context) {
 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"
 	"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) {
 func Setting(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Setting"
 	ctx.Data["Title"] = "Setting"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSetting"] = true
 	ctx.Data["IsUserPageSetting"] = true
 	ctx.Data["Owner"] = ctx.User
 	ctx.Data["Owner"] = ctx.User
-	ctx.HTML(200, "user/setting")
+	ctx.HTML(200, SETTING)
 }
 }
 
 
 func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
 func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
@@ -28,7 +37,7 @@ func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
 	ctx.Data["IsUserPageSetting"] = true
 	ctx.Data["IsUserPageSetting"] = true
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "user/setting")
+		ctx.HTML(200, SETTING)
 		return
 		return
 	}
 	}
 
 
@@ -59,7 +68,7 @@ func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
 	ctx.User.Avatar = base.EncodeMd5(form.Avatar)
 	ctx.User.Avatar = base.EncodeMd5(form.Avatar)
 	ctx.User.AvatarEmail = form.Avatar
 	ctx.User.AvatarEmail = form.Avatar
 	if err := models.UpdateUser(ctx.User); err != nil {
 	if err := models.UpdateUser(ctx.User); err != nil {
-		ctx.Handle(500, "setting.Setting", err)
+		ctx.Handle(500, "setting.Setting(UpdateUser)", err)
 		return
 		return
 	}
 	}
 	log.Trace("%s User setting updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
 	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)
 		ctx.Handle(500, "user.SettingSocial(GetOauthByUserId)", err)
 		return
 		return
 	}
 	}
-	ctx.HTML(200, "user/social")
+	ctx.HTML(200, SOCIAL)
 }
 }
 
 
 func SettingPassword(ctx *middleware.Context) {
 func SettingPassword(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Password"
 	ctx.Data["Title"] = "Password"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingPasswd"] = true
 	ctx.Data["IsUserPageSettingPasswd"] = true
-	ctx.HTML(200, "user/password")
+	ctx.HTML(200, PASSWORD)
 }
 }
 
 
 func SettingPasswordPost(ctx *middleware.Context, form auth.UpdatePasswdForm) {
 func SettingPasswordPost(ctx *middleware.Context, form auth.UpdatePasswdForm) {
@@ -106,7 +115,7 @@ func SettingPasswordPost(ctx *middleware.Context, form auth.UpdatePasswdForm) {
 	ctx.Data["IsUserPageSettingPasswd"] = true
 	ctx.Data["IsUserPageSettingPasswd"] = true
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "user/password")
+		ctx.HTML(200, PASSWORD)
 		return
 		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) {
 func SettingNotification(ctx *middleware.Context) {
@@ -215,7 +224,7 @@ func SettingNotification(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Notification"
 	ctx.Data["Title"] = "Notification"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingNotify"] = true
 	ctx.Data["IsUserPageSettingNotify"] = true
-	ctx.HTML(200, "user/notification")
+	ctx.HTML(200, NOTIFICATION)
 }
 }
 
 
 func SettingSecurity(ctx *middleware.Context) {
 func SettingSecurity(ctx *middleware.Context) {
@@ -223,5 +232,5 @@ func SettingSecurity(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Security"
 	ctx.Data["Title"] = "Security"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingSecurity"] = 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"
 	"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) {
 func SignIn(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Log In"
 	ctx.Data["Title"] = "Log In"
 
 
 	if _, ok := ctx.Session.Get("socialId").(int64); ok {
 	if _, ok := ctx.Session.Get("socialId").(int64); ok {
 		ctx.Data["IsSocialLogin"] = true
 		ctx.Data["IsSocialLogin"] = true
-		ctx.HTML(200, "user/signin")
+		ctx.HTML(200, SIGNIN)
 		return
 		return
 	}
 	}
 
 
@@ -32,23 +41,23 @@ func SignIn(ctx *middleware.Context) {
 	}
 	}
 
 
 	// Check auto-login.
 	// 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
 		return
 	}
 	}
 
 
 	isSucceed := false
 	isSucceed := false
 	defer func() {
 	defer func() {
 		if !isSucceed {
 		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.CookieUserName, "", -1)
 			ctx.SetCookie(setting.CookieRememberName, "", -1)
 			ctx.SetCookie(setting.CookieRememberName, "", -1)
 			return
 			return
 		}
 		}
 	}()
 	}()
 
 
-	user, err := models.GetUserByName(userName)
+	user, err := models.GetUserByName(uname)
 	if err != nil {
 	if err != nil {
 		ctx.Handle(500, "user.SignIn(GetUserByName)", err)
 		ctx.Handle(500, "user.SignIn(GetUserByName)", err)
 		return
 		return
@@ -57,7 +66,7 @@ func SignIn(ctx *middleware.Context) {
 	secret := base.EncodeMd5(user.Rands + user.Passwd)
 	secret := base.EncodeMd5(user.Rands + user.Passwd)
 	value, _ := ctx.GetSecureCookie(secret, setting.CookieRememberName)
 	value, _ := ctx.GetSecureCookie(secret, setting.CookieRememberName)
 	if value != user.Name {
 	if value != user.Name {
-		ctx.HTML(200, "user/signin")
+		ctx.HTML(200, SIGNIN)
 		return
 		return
 	}
 	}
 
 
@@ -86,19 +95,19 @@ func SignInPost(ctx *middleware.Context, form auth.LogInForm) {
 	}
 	}
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "user/signin")
+		ctx.HTML(200, SIGNIN)
 		return
 		return
 	}
 	}
 
 
-	user, err := models.LoginUser(form.UserName, form.Password)
+	user, err := models.UserSignIn(form.UserName, form.Password)
 	if err != nil {
 	if err != nil {
 		if err == models.ErrUserNotExist {
 		if err == models.ErrUserNotExist {
 			log.Trace("%s Log in failed: %s", ctx.Req.RequestURI, form.UserName)
 			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
 			return
 		}
 		}
 
 
-		ctx.Handle(500, "user.SignIn", err)
+		ctx.Handle(500, "user.SignInPost(UserSignIn)", err)
 		return
 		return
 	}
 	}
 
 
@@ -151,7 +160,7 @@ func SignUp(ctx *middleware.Context) {
 
 
 	if setting.Service.DisableRegistration {
 	if setting.Service.DisableRegistration {
 		ctx.Data["DisableRegistration"] = true
 		ctx.Data["DisableRegistration"] = true
-		ctx.HTML(200, "user/signup")
+		ctx.HTML(200, SIGNUP)
 		return
 		return
 	}
 	}
 
 
@@ -160,7 +169,7 @@ func SignUp(ctx *middleware.Context) {
 		return
 		return
 	}
 	}
 
 
-	ctx.HTML(200, "user/signup")
+	ctx.HTML(200, SIGNUP)
 }
 }
 
 
 func oauthSignUp(ctx *middleware.Context, sid int64) {
 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["username"] = strings.Replace(ctx.Session.Get("socialName").(string), " ", "", -1)
 	ctx.Data["email"] = ctx.Session.Get("socialEmail")
 	ctx.Data["email"] = ctx.Session.Get("socialEmail")
 	log.Trace("user.oauthSignUp(social ID): %v", ctx.Session.Get("socialId"))
 	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) {
 func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
@@ -198,14 +207,14 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
 	}
 	}
 
 
 	if ctx.HasError() {
 	if ctx.HasError() {
-		ctx.HTML(200, "user/signup")
+		ctx.HTML(200, SIGNUP)
 		return
 		return
 	}
 	}
 
 
 	if form.Password != form.RetypePasswd {
 	if form.Password != form.RetypePasswd {
 		ctx.Data["Err_Password"] = true
 		ctx.Data["Err_Password"] = true
 		ctx.Data["Err_RetypePasswd"] = 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
 		return
 	}
 	}
 
 
@@ -217,21 +226,23 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
 	}
 	}
 
 
 	var err error
 	var err error
-	if u, err = models.RegisterUser(u); err != nil {
+	if u, err = models.CreateUser(u); err != nil {
 		switch err {
 		switch err {
 		case models.ErrUserAlreadyExist:
 		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:
 		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:
 		case models.ErrUserNameIllegal:
-			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "user/signup", &form)
+			ctx.Data["Err_UserName"] = true
+			ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), SIGNUP, &form)
 		default:
 		default:
-			ctx.Handle(500, "user.SignUp(RegisterUser)", err)
+			ctx.Handle(500, "user.SignUpPost(CreateUser)", err)
 		}
 		}
 		return
 		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.
 	// Bind social account.
 	if isOauth {
 	if isOauth {
@@ -256,6 +267,7 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
 		}
 		}
 		return
 		return
 	}
 	}
+
 	ctx.Redirect("/user/login")
 	ctx.Redirect("/user/login")
 }
 }
 
 
@@ -263,7 +275,7 @@ func Delete(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Delete Account"
 	ctx.Data["Title"] = "Delete Account"
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["PageIsUserSetting"] = true
 	ctx.Data["IsUserPageSettingDelete"] = true
 	ctx.Data["IsUserPageSettingDelete"] = true
-	ctx.HTML(200, "user/delete")
+	ctx.HTML(200, DELETE)
 }
 }
 
 
 func DeletePost(ctx *middleware.Context) {
 func DeletePost(ctx *middleware.Context) {
@@ -284,7 +296,7 @@ func DeletePost(ctx *middleware.Context) {
 			case models.ErrUserOwnRepos:
 			case models.ErrUserOwnRepos:
 				ctx.Flash.Error("Your account still have ownership of repository, you have to delete or transfer them first.")
 				ctx.Flash.Error("Your account still have ownership of repository, you have to delete or transfer them first.")
 			default:
 			default:
-				ctx.Handle(500, "user.Delete", err)
+				ctx.Handle(500, "user.DeletePost(DeleteUser)", err)
 				return
 				return
 			}
 			}
 		} else {
 		} else {
@@ -319,7 +331,7 @@ func Activate(ctx *middleware.Context) {
 		} else {
 		} else {
 			ctx.Data["ServiceNotEnabled"] = true
 			ctx.Data["ServiceNotEnabled"] = true
 		}
 		}
-		ctx.HTML(200, "user/activate")
+		ctx.HTML(200, ACTIVATE)
 		return
 		return
 	}
 	}
 
 
@@ -341,7 +353,7 @@ func Activate(ctx *middleware.Context) {
 	}
 	}
 
 
 	ctx.Data["IsActivateFailed"] = true
 	ctx.Data["IsActivateFailed"] = true
-	ctx.HTML(200, "user/activate")
+	ctx.HTML(200, ACTIVATE)
 }
 }
 
 
 func ForgotPasswd(ctx *middleware.Context) {
 func ForgotPasswd(ctx *middleware.Context) {
@@ -349,12 +361,12 @@ func ForgotPasswd(ctx *middleware.Context) {
 
 
 	if setting.MailService == nil {
 	if setting.MailService == nil {
 		ctx.Data["IsResetDisable"] = true
 		ctx.Data["IsResetDisable"] = true
-		ctx.HTML(200, "user/forgot_passwd")
+		ctx.HTML(200, FORGOT_PASSWORD)
 		return
 		return
 	}
 	}
 
 
 	ctx.Data["IsResetRequest"] = true
 	ctx.Data["IsResetRequest"] = true
-	ctx.HTML(200, "user/forgot_passwd")
+	ctx.HTML(200, FORGOT_PASSWORD)
 }
 }
 
 
 func ForgotPasswdPost(ctx *middleware.Context) {
 func ForgotPasswdPost(ctx *middleware.Context) {
@@ -379,7 +391,7 @@ func ForgotPasswdPost(ctx *middleware.Context) {
 
 
 	if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
 	if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
 		ctx.Data["ResendLimited"] = true
 		ctx.Data["ResendLimited"] = true
-		ctx.HTML(200, "user/forgot_passwd")
+		ctx.HTML(200, FORGOT_PASSWORD)
 		return
 		return
 	}
 	}
 
 
@@ -391,7 +403,7 @@ func ForgotPasswdPost(ctx *middleware.Context) {
 	ctx.Data["Email"] = email
 	ctx.Data["Email"] = email
 	ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
 	ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
 	ctx.Data["IsResetSent"] = true
 	ctx.Data["IsResetSent"] = true
-	ctx.HTML(200, "user/forgot_passwd")
+	ctx.HTML(200, FORGOT_PASSWORD)
 }
 }
 
 
 func ResetPasswd(ctx *middleware.Context) {
 func ResetPasswd(ctx *middleware.Context) {
@@ -404,7 +416,7 @@ func ResetPasswd(ctx *middleware.Context) {
 	}
 	}
 	ctx.Data["Code"] = code
 	ctx.Data["Code"] = code
 	ctx.Data["IsResetForm"] = true
 	ctx.Data["IsResetForm"] = true
-	ctx.HTML(200, "user/reset_passwd")
+	ctx.HTML(200, RESET_PASSWORD)
 }
 }
 
 
 func ResetPasswdPost(ctx *middleware.Context) {
 func ResetPasswdPost(ctx *middleware.Context) {
@@ -441,5 +453,5 @@ func ResetPasswdPost(ctx *middleware.Context) {
 	}
 	}
 
 
 	ctx.Data["IsResetFailed"] = true
 	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}}">
                     <div class="form-group {{if .Err_Attributes}}has-error has-feedback{{end}}">
                         <label class="col-md-3 control-label">Search Attributes: </label>
                         <label class="col-md-3 control-label">Search Attributes: </label>
                         <div class="col-md-7">
                         <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>
                     </div>
 
 
                     <div class="form-group {{if .Err_Filter}}has-error has-feedback{{end}}">
                     <div class="form-group {{if .Err_Filter}}has-error has-feedback{{end}}">
                         <label class="col-md-3 control-label">Search Filter: </label>
                         <label class="col-md-3 control-label">Search Filter: </label>
                         <div class="col-md-7">
                         <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>
                     </div>
 
 
                     <div class="form-group {{if .Err_MsAdSA}}has-error has-feedback{{end}}">
                     <div class="form-group {{if .Err_MsAdSA}}has-error has-feedback{{end}}">
                         <label class="col-md-3 control-label">Ms Ad SA: </label>
                         <label class="col-md-3 control-label">Ms Ad SA: </label>
                         <div class="col-md-7">
                         <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>
                     </div>
                     </div>
                     {{else if eq $type 3}}
                     {{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>
                     <dd>{{.LogRootPath}}</dd>
                     <dt>Script Type</dt>
                     <dt>Script Type</dt>
                     <dd>{{.ScriptType}}</dd>
                     <dd>{{.ScriptType}}</dd>
+                    <dt>Reverse Authentication User</dt>
+                    <dd>{{.ReverseProxyAuthUser}}</dd>
                 </dl>
                 </dl>
             </div>
             </div>
         </div>
         </div>
@@ -77,7 +79,7 @@
                     <dt>Require Sign In View</dt>
                     <dt>Require Sign In View</dt>
                     <dd><i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></dd>
                     <dd><i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></dd>
                     <dt>Mail Notification</dt>
                     <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>
                     <dt>Enable Cache Avatar</dt>
                     <dd><i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></dd>
                     <dd><i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></dd>
                     <hr/>
                     <hr/>
@@ -89,6 +91,21 @@
             </div>
             </div>
         </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 panel-default">
             <div class="panel-heading">
             <div class="panel-heading">
                 Mailer Configuration
                 Mailer Configuration

+ 4 - 0
templates/admin/dashboard.tmpl

@@ -32,6 +32,10 @@
                             <td>Clean unbind OAuthes</td>
                             <td>Clean unbind OAuthes</td>
                             <td><i class="fa fa-caret-square-o-right"></i> <a href="/admin?op=1">Run</a></td>
                             <td><i class="fa fa-caret-square-o-right"></i> <a href="/admin?op=1">Run</a></td>
                         </tr>
                         </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>
                     </tbody>
                 </table>
                 </table>
             </div>
             </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 .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 .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 .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>
     </ul>
 </div>
 </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 -->
 		 <!-- Stylesheets -->
 		{{if CdnMode}}
 		{{if CdnMode}}
 		<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
 		<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="//code.jquery.com/jquery-1.11.0.min.js"></script>
 		<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.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">
         <div class="form-group">
             <label class="col-md-2 control-label">Owner<strong class="text-danger">*</strong></label>
             <label class="col-md-2 control-label">Owner<strong class="text-danger">*</strong></label>
             <div class="col-md-8">
             <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>
             </div>
+            <input type="hidden" value="{{.SignedUserId}}" name="uid" id="repo-owner-id"/>
         </div>
         </div>
 
 
         <div class="form-group {{if .Err_RepoName}}has-error has-feedback{{end}}">
         <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