diff options
93 files changed, 2144 insertions, 2020 deletions
@@ -1,6 +1,6 @@ [run] init_cmds = [ - ["grep", "-rn", "FIXME", "."], + #["grep", "-rn", "FIXME", "."], ["./gogs", "web"] ] watch_all = true @@ -1,6 +1,7 @@ .DS_Store *.db *.log +log/ custom/ data/ .vendor/ @@ -34,4 +35,4 @@ docker/fig.yml docker/docker/Dockerfile docker/docker/init_gogs.sh gogs.sublime-project -gogs.sublime-workspace
\ No newline at end of file +gogs.sublime-workspace @@ -2,35 +2,34 @@ path = github.com/gogits/gogs [deps] -github.com/beego/memcache = commit:2aea774416 -github.com/bradfitz/gomemcache = +github.com/bradfitz/gomemcache = commit:72a68649ba github.com/Unknwon/cae = commit:2e70a1351b -github.com/Unknwon/com = commit:d9bcf409c8 +github.com/Unknwon/com = commit:188d690b1a github.com/Unknwon/i18n = commit:1e88666229 -github.com/Unknwon/macaron = -github.com/codegangsta/cli = commit:a14c5b47c7 -github.com/go-sql-driver/mysql = commit:04cf947760 -github.com/go-xorm/core = commit:e7882d8b00 -github.com/go-xorm/xorm = commit:dcc529b68a +github.com/Unknwon/macaron = commit:e089393c3f +github.com/codegangsta/cli = commit:6086d7927e +github.com/go-sql-driver/mysql = commit:27633f0519 +github.com/go-xorm/core = commit:16cb27928f +github.com/go-xorm/xorm = commit:f2d3be988e github.com/gogits/chardet = commit:2404f77725 github.com/gogits/go-gogs-client = commit:92e76d616a -github.com/lib/pq = commit:3e3efe51a0 -github.com/macaron-contrib/binding = commit:0fbe4b9707 -github.com/macaron-contrib/cache = -github.com/macaron-contrib/captcha = commit:3567dc48b8 -github.com/macaron-contrib/csrf = commit:3ea14e7ee7 -github.com/macaron-contrib/i18n = commit:0ee0539c84 +github.com/lib/pq = commit:835d5eb08d +github.com/macaron-contrib/binding = commit:dc739fabc3 +github.com/macaron-contrib/cache = commit:b68f6b448f +github.com/macaron-contrib/captcha = commit:066c50c7eb +github.com/macaron-contrib/csrf = commit:98ddf5a710 +github.com/macaron-contrib/i18n = commit:eeebd44f64 github.com/macaron-contrib/oauth2 = commit:8f394c3629 -github.com/macaron-contrib/session = -github.com/macaron-contrib/toolbox = commit:57127bcc89 -github.com/mattn/go-sqlite3 = commit:a80c27ba33 -github.com/microcosm-cc/bluemonday = +github.com/macaron-contrib/session = commit:8e8d938b27 +github.com/macaron-contrib/toolbox = commit:acbfe36e16 +github.com/mattn/go-sqlite3 = commit:25d045f12a +github.com/microcosm-cc/bluemonday = commit:fcd0f5074e github.com/nfnt/resize = commit:8f44931448 -github.com/russross/blackfriday = commit:05b8cefd6a -github.com/shurcooL/go = commit:48293cbc7a +github.com/russross/blackfriday = commit:77efab57b2 +github.com/shurcooL/go = commit:329f57438c golang.org/x/net = golang.org/x/text = -gopkg.in/ini.v1 = commit:28ad8c408b +gopkg.in/ini.v1 = commit:4febc4104c gopkg.in/redis.v2 = commit:e617904962 [res] diff --git a/.travis.yml b/.travis.yml index 85e5f396..4149e173 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,5 @@ script: go build -v notifications: email: - - u@gogs.io
\ No newline at end of file + - u@gogs.io + slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx
\ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3619018..411a8e93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,20 +2,20 @@ > This guidelines sheet is forked from [CONTRIBUTING.md](https://github.com/drone/drone/blob/master/CONTRIBUTING.md). -Gogs is not perfect and it has bugs, or incomplete features for rare cases. You're welcome to tell us or contribute some code. This document describes details about how can you contribute to Gogs project. +Gogs is not perfect, and it has bugs or incomplete features in rare cases. You're welcome to tell us, or to contribute some code. This document describes details about how can you contribute to Gogs project. ## Contribution guidelines Depends on the situation, you will: -- Find bug, create an issue -- Need more functionality, make a feature request -- Want to contribute code, open a pull request -- Run into issue, need help +- Find a bug and create an issue +- Need more functionality and make a feature request +- Want to contribute code and open a pull request +- Run into issue and need help ### Bug Report -If you find or consider something is a bug, please create a issue on [GitHub](https://github.com/gogits/gogs/issues). To reduce unnecessary time wasting of interacting and waiting with team members, please include following information in the first place with a comfortable form for you: +If you find something you consider a bug, please create a issue on [GitHub](https://github.com/gogits/gogs/issues). To avoid wasting time and reduce back-and-forth communication with team members, please include at least the following information in a form comfortable for you: - Bug Description - Gogs Version @@ -28,7 +28,7 @@ Please take a moment to check that an issue on [GitHub](https://github.com/gogit #### Bug Report Example -Gogs crashed when create repository with license, using v0.5.13.0207, SQLite3, Git 1.9.0, Ubuntu 12.04. +Gogs crashed when creating a repository with a license, using v0.5.13.0207, SQLite3, Git 1.9.0, Ubuntu 12.04. Error log: @@ -38,11 +38,11 @@ Error log: ### Feature Request -There is no standard form of making a feature request, just try to describe the feature as clear as possible because team members may not have experience with the functionality you're talking about. +There is no standard form of making a feature request. Just try to describe the feature as clearly as possible, because team members may not have experience with the functionality you're talking about. ### Pull Request -Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST SEND TO `DEV` BRANCH**. +Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `DEV` BRANCH**. We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it. @@ -52,7 +52,7 @@ We're trying very hard to keep Gogs lean and focused. We don't want it to do eve ### Ask For Help -Before open any new issue, please check your problem on [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md) and [FAQs](http://gogs.io/docs/intro/faqs.html) pages. +Before opening an issue, please make sure your problem isn't already addressed on the [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md) and [FAQs](http://gogs.io/docs/intro/faqs.html) pages. ## Things To Notice @@ -3,18 +3,18 @@ Gogs - Go Git Service [](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Gogs(Go Git Service) is a painless self-hosted Git Service written in Go. +Gogs (Go Git Service) is a painless self-hosted Git service written in Go.  -##### Current version: 0.5.13 Beta +##### Current version: 0.5.16 Beta ### NOTICES - Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site. -- Demo site [try.gogs.io](https://try.gogs.io) is running under `dev` branch. -- You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing a issue or making a Pull Request. -- If you think there are vulnerabilities in the project, please talk private to **u@gogs.io**, thanks! +- The demo site [try.gogs.io](https://try.gogs.io) is running under `dev` branch. +- You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request. +- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks! #### Other language version @@ -22,15 +22,15 @@ Gogs(Go Git Service) is a painless self-hosted Git Service written in Go. ## Purpose -The goal of this project is to make the easiest, fastest and most painless way to set up a self-hosted Git service. With Go, this can be done in independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows. +The goal of this project is to make the easiest, fastest, and most painless way to set up a self-hosted Git service. With Go, this can be done via an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows. ## Overview -- Please see [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log. -- See [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team. -- Try it before anything? Do it [online](https://try.gogs.io/unknwon/gogs) or go down to **Installation -> Install from binary** section! -- Having troubles? Get help from [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md). -- Want to help on localization? Check out [Crowdin](https://crowdin.com/project/gogs)! +- Please see the [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log. +- See the [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team. +- Want to try it before doing anything else? Do it [online](https://try.gogs.io/unknwon/gogs) or go down to the **Installation -> Install from binary** section! +- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md). +- Want to help with localization? Check out [Crowdin](https://crowdin.com/project/gogs)! ## Features @@ -46,22 +46,22 @@ The goal of this project is to make the easiest, fastest and most painless way t - Repository Git hooks - Add/remove repository collaborators - Gravatar and cache support -- Mail service(register, issue) +- Mail service (register, issue) - Administration panel - Slack webhook integration - Drone CI integration - Supports MySQL, PostgreSQL and SQLite3 -- Social account login(GitHub, Google, QQ, Weibo) -- Multi-language support([10 languages](https://crowdin.com/project/gogs)) +- Social account login (GitHub, Google, QQ, Weibo) +- Multi-language support ([10 languages](https://crowdin.com/project/gogs)) ## System Requirements -- A cheap Raspberry Pi is powerful enough to match the minimal requirement. -- 4 CPU Cores and 1GB RAM would be the baseline for teamwork. +- A cheap Raspberry Pi is powerful enough for basic functionality. +- At least 2 CPU cores and 1GB RAM would be the baseline for teamwork. ## Installation -Make sure you install [Prerequirements](http://gogs.io/docs/installation/) first. +Make sure you install the [prerequisites](http://gogs.io/docs/installation/) first. There are 5 ways to install Gogs: diff --git a/README_ZH.md b/README_ZH.md index b6d74e7b..1d2cfa3e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。  -##### 当前版本:0.5.13 Beta +##### 当前版本:0.5.16 Beta ## 开发目的 @@ -44,7 +44,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 ## 系统要求 - 最低的系统硬件要求为一个廉价的树莓派 -- 如果用于团队项目,建议使用 4 核 CPU 及 1GB 内存 +- 如果用于团队项目,建议使用 2 核 CPU 及 1GB 内存 ## 安装部署 @@ -75,4 +75,4 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 ## 授权许可 -本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。
\ No newline at end of file +本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。 diff --git a/cmd/serve.go b/cmd/serve.go index 25f7dd91..ded4392a 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -8,7 +8,6 @@ import ( "fmt" "os" "os/exec" - "path" "path/filepath" "strings" "time" @@ -22,6 +21,10 @@ import ( "github.com/gogits/gogs/modules/uuid" ) +const ( + _ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access" +) + var CmdServ = cli.Command{ Name: "serv", Usage: "This command should only be called by SSH shell", @@ -43,7 +46,7 @@ func setup(logPath string) { models.LoadModelsConfig() - if models.UseSQLite3 { + if setting.UseSQLite3 { workDir, _ := setting.WorkDir() os.Chdir(workDir) } @@ -56,67 +59,55 @@ func parseCmd(cmd string) (string, string) { if len(ss) != 2 { return "", "" } - - verb, args := ss[0], ss[1] - if verb == "git" { - ss = strings.SplitN(args, " ", 2) - args = ss[1] - verb = fmt.Sprintf("%s %s", verb, ss[0]) - } - return verb, strings.Replace(args, "'/", "'", 1) + return ss[0], strings.Replace(ss[1], "'/", "'", 1) } var ( - 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]models.AccessType{ - "git-receive-pack": models.READABLE, - "git receive-pack": models.READABLE, + COMMANDS = map[string]models.AccessMode{ + "git-upload-pack": models.ACCESS_MODE_READ, + "git upload-pack": models.ACCESS_MODE_READ, + "git-upload-archive": models.ACCESS_MODE_READ, + "git-receive-pack": models.ACCESS_MODE_WRITE, + "git receive-pack": models.ACCESS_MODE_WRITE, } ) -func In(b string, sl map[string]models.AccessType) bool { - _, e := sl[b] - return e -} - -func runServ(k *cli.Context) { - if k.IsSet("config") { - setting.CustomConf = k.String("config") +func runServ(c *cli.Context) { + if c.IsSet("config") { + setting.CustomConf = c.String("config") } setup("serv.log") - if len(k.Args()) < 1 { - log.GitLogger.Fatal(2, "Not enough arguments") + fail := func(userMessage, logMessage string, args ...interface{}) { + fmt.Fprintln(os.Stderr, "Gogs: ", userMessage) + log.GitLogger.Fatal(2, logMessage, args...) + } + + if len(c.Args()) < 1 { + fail("Not enough arguments", "Not enough arugments") } - keys := strings.Split(k.Args()[0], "-") + + keys := strings.Split(c.Args()[0], "-") if len(keys) != 2 { - println("Gogs: auth file format error") - log.GitLogger.Fatal(2, "Invalid auth file format: %s", os.Args[2]) + fail("key-id format error", "Invalid key id: %s", c.Args()[0]) } keyId, err := com.StrTo(keys[1]).Int64() if err != nil { - println("Gogs: auth file format error") - log.GitLogger.Fatal(2, "Invalid auth file format: %v", err) + fail("key-id format error", "Invalid key id: %s", err) } + user, err := models.GetUserByKeyId(keyId) if err != nil { - if err == models.ErrUserNotKeyOwner { - println("Gogs: you are not the owner of SSH key") - log.GitLogger.Fatal(2, "Invalid owner of SSH key: %d", keyId) - } - println("Gogs: internal error:", err.Error()) - log.GitLogger.Fatal(2, "Fail to get user by key ID(%d): %v", keyId, err) + fail("internal error", "Fail to get user by key ID(%d): %v", keyId, err) } cmd := os.Getenv("SSH_ORIGINAL_COMMAND") if cmd == "" { println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.") + if user.IsAdmin { + println("If this is unexpected, please log in with password and setup Gogs under another user.") + } return } @@ -124,62 +115,47 @@ func runServ(k *cli.Context) { repoPath := strings.Trim(args, "'") rr := strings.SplitN(repoPath, "/", 2) if len(rr) != 2 { - println("Gogs: unavailable repository", args) - log.GitLogger.Fatal(2, "Unavailable repository: %v", args) + fail("Invalid repository path", "Invalide repository path: %v", args) } repoUserName := rr[0] repoName := strings.TrimSuffix(rr[1], ".git") - isWrite := In(verb, COMMANDS_WRITE) - isRead := In(verb, COMMANDS_READONLY) - repoUser, err := models.GetUserByName(repoUserName) if err != nil { if err == models.ErrUserNotExist { - println("Gogs: given repository owner are not registered") - log.GitLogger.Fatal(2, "Unregistered owner: %s", repoUserName) + fail("Repository owner does not exist", "Unregistered owner: %s", repoUserName) } - println("Gogs: internal error:", err.Error()) - log.GitLogger.Fatal(2, "Fail to get repository owner(%s): %v", repoUserName, err) + fail("Internal error", "Fail to get repository owner(%s): %v", repoUserName, err) } - // Access check. - switch { - case isWrite: - has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE) - if err != nil { - println("Gogs: internal error:", err.Error()) - log.GitLogger.Fatal(2, "Fail to check write access:", err) - } else if !has { - println("You have no right to write this repository") - log.GitLogger.Fatal(2, "User %s has no right to write repository %s", user.Name, repoPath) - } - case isRead: - repo, err := models.GetRepositoryByName(repoUser.Id, repoName) - if err != nil { - if err == models.ErrRepoNotExist { - println("Gogs: given repository does not exist") - log.GitLogger.Fatal(2, "Repository does not exist: %s/%s", repoUser.Name, repoName) + repo, err := models.GetRepositoryByName(repoUser.Id, repoName) + if err != nil { + if err == models.ErrRepoNotExist { + if user.Id == repoUser.Id || repoUser.IsOwnedBy(user.Id) { + fail("Repository does not exist", "Repository does not exist: %s/%s", repoUser.Name, repoName) + } else { + fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, repoName) } - println("Gogs: internal error:", err.Error()) - log.GitLogger.Fatal(2, "Fail to get repository: %v", err) } + fail("Internal error", "Fail to get repository: %v", err) + } - if !repo.IsPrivate { - break - } + requestedMode, has := COMMANDS[verb] + if !has { + fail("Unknown git command", "Unknown git command %s", verb) + } - has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE) - if err != nil { - println("Gogs: internal error:", err.Error()) - log.GitLogger.Fatal(2, "Fail to check read access:", err) - } else if !has { - println("You have no right to access this repository") - log.GitLogger.Fatal(2, "User %s has no right to read repository %s", user.Name, repoPath) + mode, err := models.AccessLevel(user, repo) + if err != nil { + fail("Internal error", "Fail to check access: %v", err) + } else if mode < requestedMode { + clientMessage := _ACCESS_DENIED_MESSAGE + if mode >= models.ACCESS_MODE_READ { + clientMessage = "You do not have sufficient authorization for this action" } - default: - println("Unknown command: " + cmd) - return + fail(clientMessage, + "User %s does not have level %v access to repository %s", + user.Name, requestedMode, repoPath) } uuid := uuid.NewV4().String() @@ -197,11 +173,10 @@ func runServ(k *cli.Context) { gitcmd.Stdin = os.Stdin gitcmd.Stderr = os.Stderr if err = gitcmd.Run(); err != nil { - println("Gogs: internal error:", err.Error()) - log.GitLogger.Fatal(2, "Fail to execute git command: %v", err) + fail("Internal error", "Fail to execute git command: %v", err) } - if isWrite { + if requestedMode == models.ACCESS_MODE_WRITE { tasks, err := models.GetUpdateTasksByUuid(uuid) if err != nil { log.GitLogger.Fatal(2, "GetUpdateTasksByUuid: %v", err) @@ -223,10 +198,10 @@ func runServ(k *cli.Context) { // Update key activity. key, err := models.GetPublicKeyById(keyId) if err != nil { - log.GitLogger.Fatal(2, "GetPublicKeyById: %v", err) + fail("Internal error", "GetPublicKeyById: %v", err) } key.Updated = time.Now() if err = models.UpdatePublicKey(key); err != nil { - log.GitLogger.Fatal(2, "UpdatePublicKey: %v", err) + fail("Internal error", "UpdatePublicKey: %v", err) } } @@ -79,7 +79,7 @@ func checkVersion() { // Check dependency version. checkers := []VerChecker{ {"github.com/Unknwon/macaron", macaron.Version, "0.5.1"}, - {"github.com/macaron-contrib/binding", binding.Version, "0.0.4"}, + {"github.com/macaron-contrib/binding", binding.Version, "0.0.5"}, {"github.com/macaron-contrib/cache", cache.Version, "0.0.7"}, {"github.com/macaron-contrib/csrf", csrf.Version, "0.0.3"}, {"github.com/macaron-contrib/i18n", i18n.Version, "0.0.5"}, @@ -166,12 +166,11 @@ func newMacaron() *macaron.Macaron { } func runWeb(ctx *cli.Context) { - checkVersion() - if ctx.IsSet("config") { setting.CustomConf = ctx.String("config") } routers.GlobalInit() + checkVersion() m := newMacaron() @@ -230,7 +229,7 @@ func runWeb(ctx *cli.Context) { }) m.Any("/*", func(ctx *middleware.Context) { - ctx.JSON(404, &base.ApiJsonErr{"Not Found", base.DOC_URL}) + ctx.HandleAPI(404, "Page not found") }) }) }) @@ -319,7 +318,7 @@ func runWeb(ctx *cli.Context) { m.Get("/template/*", dev.TemplatePreview) } - reqTrueOwner := middleware.RequireTrueOwner() + reqAdmin := middleware.RequireAdmin() // Organization. m.Group("/org", func() { @@ -394,7 +393,7 @@ func runWeb(ctx *cli.Context) { m.Post("/:name", repo.GitHooksEditPost) }, middleware.GitHookService()) }) - }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) + }, reqSignIn, middleware.RepoAssignment(true), reqAdmin) m.Group("/:username/:reponame", func() { m.Get("/action/:action", repo.Action) diff --git a/conf/app.ini b/conf/app.ini index 6f4ae897..ad38c42e 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -90,7 +90,7 @@ TASK_INTERVAL = 1 ; Deliver timeout in seconds DELIVER_TIMEOUT = 5 ; Allow insecure certification -ALLOW_INSECURE_CERTIFICATION = false +SKIP_TLS_VERIFY = false [mailer] ENABLED = false diff --git a/conf/locale/locale_de-DE.ini b/conf/locale/locale_de-DE.ini index 1dc7523b..31ee931b 100755 --- a/conf/locale/locale_de-DE.ini +++ b/conf/locale/locale_de-DE.ini @@ -647,6 +647,7 @@ config.reset_password_code_lives=Passwortcode Lebensdauer config.webhook_config=Webhook-Einstellungen
config.task_interval=Task-Intervall
config.deliver_timeout=Zeitlimit für Zustellung
+config.skip_tls_verify=Skip TLS Verify
config.mailer_config=Mailer-Einstellungen
config.mailer_enabled=Aktiviert
config.mailer_name=Name
diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 9e691171..7e32a21e 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -149,7 +149,7 @@ repo_name_been_taken = Repository name has been already taken. org_name_been_taken = Organization name has been already taken. team_name_been_taken = Team name has been already taken. email_been_used = E-mail address has been already used. -ssh_key_been_used = Public key name has been used. +ssh_key_been_used = Public key name or content has been used. illegal_username = Your username contains illegal characters. illegal_repo_name = Repository name contains illegal characters. illegal_org_name = Organization name contains illegal characters. @@ -281,13 +281,13 @@ init_readme = Initialize this repository with a README.md create_repo = Create Repository default_branch = Default Branch mirror_interval = Mirror Interval (hour) -goget_meta = Go-Get Meta -goget_meta_helper = This repository will be <span class="label label-blue label-radius">Go-Getable</span> need_auth = Need Authorization migrate_type = Migration Type migrate_type_helper = This repository will be a <span class="label label-blue label-radius">Mirror</span> migrate_repo = Migrate Repository +migrate.clone_address = Clone Address +migrate.invalid_local_path = Invalid local path, it does not exist or not a directory. copy_link = Copy click_to_copy = Copy to clipboard @@ -595,7 +595,10 @@ auths.domain = Domain auths.host = Host auths.port = Port auths.base_dn = Base DN -auths.attributes = Search Attributes +auths.attribute_username = Username attribute +auths.attribute_name = First name attribute +auths.attribute_surname = Surname attribute +auths.attribute_mail = E-mail attribute auths.filter = Search Filter auths.ms_ad_sa = Ms Ad SA auths.smtp_auth = SMTP Authorization Type @@ -647,7 +650,7 @@ config.reset_password_code_lives = Reset Password Code Lives config.webhook_config = Webhook Configuration config.task_interval = Task Interval config.deliver_timeout = Deliver Timeout -config.allow_insecure_certification = Allow Insecure Certification +config.skip_tls_verify = Skip TLS Verify config.mailer_config = Mailer Configuration config.mailer_enabled = Enabled config.mailer_name = Name diff --git a/conf/locale/locale_es-ES.ini b/conf/locale/locale_es-ES.ini index d7fab5f9..fc235f15 100755 --- a/conf/locale/locale_es-ES.ini +++ b/conf/locale/locale_es-ES.ini @@ -516,8 +516,8 @@ dashboard.git_gc_repos=Ejecutar la recolección de basura en los repositorios dashboard.git_gc_repos_success=Todos los repositorios han ejecutado correctamente el recolector de basuras.
dashboard.resync_all_sshkeys=Reescribir el fichero '.ssh/authorized_key'(atención: se perderán las claves que no pertenezcan a Gogs)
dashboard.resync_all_sshkeys_success=Todas las claves públicas se han reescrito correctamente.
-dashboard.resync_all_update_hooks=Rewrite all update hook of repositories (needed when custom config path is changed)
-dashboard.resync_all_update_hooks_success=All repositories' update hook have been rewritten successfully.
+dashboard.resync_all_update_hooks=Reescribir todos los hooks de actualización de los repositorios (necesario cuando se modifica la ruta de configuración personalizada)
+dashboard.resync_all_update_hooks_success=Todos los hooks de actualización de los repositorios se han reescrito correctamente.
dashboard.server_uptime=Uptime del Servidor
dashboard.current_goroutine=Gorutinas Actuales
@@ -547,62 +547,62 @@ dashboard.last_gc_time=Tiempo desde el Último GC dashboard.total_gc_time=Pausa Total por GC
dashboard.total_gc_pause=Pausa Total por GC
dashboard.last_gc_pause=Última Pausa por GC
-dashboard.gc_times=GC Times
-
-users.user_manage_panel=User Manage Panel
-users.new_account=Create New Account
-users.name=Name
-users.activated=Activated
-users.admin=Admin
-users.repos=Repos
-users.created=Created
-users.edit=Edit
-users.auth_source=Authorization Source
+dashboard.gc_times=Ejecuciones GC
+
+users.user_manage_panel=Panel de Gestión de Usuarios
+users.new_account=Crear Nueva Cuenta
+users.name=Nombre
+users.activated=Activado
+users.admin=Administrador
+users.repos=Repositorios
+users.created=Creado
+users.edit=Editar
+users.auth_source=Origen de Autorización
users.local=Local
-users.auth_login_name=Authorization Login Name
-users.update_profile_success=Account profile has been updated successfully.
+users.auth_login_name=Nombre de Usuario de Autorización
+users.update_profile_success=El perfil de la cuenta se ha actualizado correctamente.
users.edit_account=Editar Cuenta
users.is_activated=Esta cuenta está activada
-users.is_admin=This account has administrator permissions
-users.allow_git_hook=This account has permissions to create Git hooks
-users.update_profile=Update Account Profile
-users.delete_account=Delete This Account
-users.still_own_repo=This account still have ownership of repository, you have to delete or transfer them first.
-users.still_has_org=This account still have membership of organization, you have to left or delete them first.
-
-orgs.org_manage_panel=Organization Manage Panel
-orgs.name=Name
-orgs.teams=Teams
-orgs.members=Members
-
-repos.repo_manage_panel=Repository Manage Panel
-repos.owner=Owner
-repos.name=Name
-repos.private=Private
-repos.watches=Watches
-repos.stars=Stars
-repos.issues=Issues
-
-auths.auth_manage_panel=Authorization Manage Panel
-auths.new=Add New Authorization Source
-auths.name=Name
-auths.type=Type
-auths.enabled=Enabled
-auths.updated=Updated
-auths.auth_type=Authorization Type
-auths.auth_name=Authorization Name
-auths.domain=Domain
+users.is_admin=Esta cuenta tiene permisos de administrador
+users.allow_git_hook=Esta cuenta tiene permisos para crear hooks de Git
+users.update_profile=Actualizar Perfil de la Cuenta
+users.delete_account=Eliminar esta Cuenta
+users.still_own_repo=Esta cuenta es propietaria de uno o más repositorios, tienes que borrarlos o transferirlos primero.
+users.still_has_org=Esta cuenta es miembro de una o más organizaciones, tienes que abandonarlas o eliminarlas primero.
+
+orgs.org_manage_panel=Panel de Gestión de Organización
+orgs.name=Nombre
+orgs.teams=Equipos
+orgs.members=Miembros
+
+repos.repo_manage_panel=Panel de Gestión de Repositorios
+repos.owner=Propietario
+repos.name=Nombre
+repos.private=Privado
+repos.watches=Vigilantes
+repos.stars=Estrellas
+repos.issues=Incidencias
+
+auths.auth_manage_panel=Panel de Gestión de Autorizaciones
+auths.new=Añadir nuevo origen de autorización
+auths.name=Nombre
+auths.type=Tipo
+auths.enabled=Activo
+auths.updated=Actualizado
+auths.auth_type=Tipo de Autorización
+auths.auth_name=Nombre de Autorización
+auths.domain=Dominio
auths.host=Host
-auths.port=Port
+auths.port=Puerto
auths.base_dn=Base DN
-auths.attributes=Search Attributes
-auths.filter=Search Filter
+auths.attributes=Atributos de búsqueda
+auths.filter=Filtro de Búsqueda
auths.ms_ad_sa=Ms Ad SA
-auths.smtp_auth=SMTP Authorization Type
+auths.smtp_auth=Tipo de Autorización SMTP
auths.smtphost=SMTP Host
-auths.smtpport=SMTP Port
-auths.enable_tls=Enable TLS Encryption
-auths.enable_auto_register=Enable Auto Registration
+auths.smtpport=Puerto SMTP
+auths.enable_tls=Habilitar Cifrado TLS
+auths.enable_auto_register=Hablilitar Auto-Registro
auths.tips=Consejos
auths.edit=Editar la Configuración de Autorización
auths.activated=Esta autenticación ha sido activada
@@ -625,7 +625,7 @@ config.repo_root_path=Ruta del Repositorio config.static_file_root_path=Ruta de los Ficheros Estáticos
config.log_file_root_path=Ruta de los Ficheros de Log
config.script_type=Tipo de Script
-config.reverse_auth_user=Reverse Authentication User
+config.reverse_auth_user=Autenticación Inversa de Usuario
config.db_config=Configuración de la Base de Datos
config.db_type=Tipo
config.db_host=Host
@@ -642,82 +642,83 @@ config.show_registration_button=Mostrar Botón de Registro config.require_sign_in_view=Solicitar la Vista de Inicio de Sesión
config.mail_notify=Notificación por Correo Electrónico
config.enable_cache_avatar=Activar la Caché de Avatar
-config.active_code_lives=Active Code Lives
-config.reset_password_code_lives=Reset Password Code Lives
-config.webhook_config=Webhook Configuration
-config.task_interval=Task Interval
-config.deliver_timeout=Deliver Timeout
-config.mailer_config=Mailer Configuration
-config.mailer_enabled=Enabled
-config.mailer_name=Name
+config.active_code_lives=Habilitar Vida del Código
+config.reset_password_code_lives=Restablecer Contraseña de Vida del Código
+config.webhook_config=Configuración de Webhooks
+config.task_interval=Intervalo de Tareas
+config.deliver_timeout=Timeout de Entrega
+config.skip_tls_verify=Omitir la Verificación TLS
+config.mailer_config=Configuración del Mailer
+config.mailer_enabled=Activado
+config.mailer_name=Nombre
config.mailer_host=Host
-config.mailer_user=User
-config.oauth_config=OAuth Configuration
-config.oauth_enabled=Enabled
-config.cache_config=Cache Configuration
-config.cache_adapter=Cache Adapter
-config.cache_interval=Cache Interval
-config.cache_conn=Cache Connection
-config.session_config=Session Configuration
-config.session_provider=Session Provider
-config.provider_config=Provider Config
-config.cookie_name=Cookie Name
-config.enable_set_cookie=Enable Set Cookie
-config.gc_interval_time=GC Interval Time
-config.session_life_time=Session Life Time
-config.https_only=HTTPS Only
-config.cookie_life_time=Cookie Life Time
-config.picture_config=Picture Configuration
-config.picture_service=Picture Service
-config.disable_gravatar=Disable Gravatar
-config.log_config=Log Configuration
-config.log_mode=Log Mode
-
-monitor.cron=Cron Tasks
-monitor.name=Name
-monitor.schedule=Schedule
-monitor.next=Next Time
-monitor.previous=Previous Time
-monitor.execute_times=Execute Times
-monitor.process=Running Processes
-monitor.desc=Description
-monitor.start=Start Time
-monitor.execute_time=Execution Time
-
-notices.system_notice_list=System Notices
-notices.type=Type
-notices.type_1=Repository
-notices.desc=Description
+config.mailer_user=Usuario
+config.oauth_config=Configuración OAuth
+config.oauth_enabled=Activado
+config.cache_config=Configuración de la Caché
+config.cache_adapter=Adaptador de la Caché
+config.cache_interval=Intervalo de la Caché
+config.cache_conn=Conexión de la Caché
+config.session_config=Configuración de la Sesión
+config.session_provider=Proveedor de la Sesión
+config.provider_config=Configuración del Proveedor
+config.cookie_name=Nombre de la Cookie
+config.enable_set_cookie=Activar Establecimiento de Cookie
+config.gc_interval_time=Intervalo de tiempo del GC
+config.session_life_time=Tiempo de Vida de la Sesión
+config.https_only=Sólo HTTPS
+config.cookie_life_time=Tiempo de Vida de la Cookie
+config.picture_config=Configuración de Imagen
+config.picture_service=Servicio de Imágen
+config.disable_gravatar=Desactivar Gravatar
+config.log_config=Configuración del Log
+config.log_mode=Modo del Log
+
+monitor.cron=Tareas de Cron
+monitor.name=Nombre
+monitor.schedule=Agenda
+monitor.next=Próxima Vez
+monitor.previous=Vez Anterior
+monitor.execute_times=Ejecuciones
+monitor.process=Procesos en Ejecución
+monitor.desc=Descripción
+monitor.start=Hora de Inicio
+monitor.execute_time=Tiempo de ejecución
+
+notices.system_notice_list=Notificaciones del Sistema
+notices.type=Tipo
+notices.type_1=Repositorio
+notices.desc=Descripción
notices.op=Op.
-notices.delete_success=System notice has been deleted successfully.
+notices.delete_success=La notificación del sistema se ha eliminado correctamente.
[action]
-create_repo=created repository <a href="%s/%s">%s</a>
-commit_repo=pushed to <a href="%s/%s/src/%s">%s</a> at <a href="%s/%s">%s</a>
-create_issue=`opened issue <a href="%s/issues/%s">%[1]s#%[2]s</a>`
-comment_issue=`commented on issue <a href="%s/issues/%s">%[1]s#%[2]s</a>`
-transfer_repo=transfered repository <code>%s</code> to <a href="/%s%s">%s</a>
-push_tag=pushed tag <a href="%s/%s/src/%s">%s</a> to <a href="%s/%s">%s</a>
-compare_2_commits=View comparison for these 2 commits
+create_repo=Repositorio creado <a href="%s/%s">%s</a>
+commit_repo=hizo push a <a href="%s/%s/src/%s">%s</a> en <a href="%s/%s">%s</a>
+create_issue=`incidencia abierta <a href="%s/issues/%s">%[1]s#%[2]s</a>`
+comment_issue=`comentó en la incidencia <a href="%s/issues/%s">%[1]s#%[2]s</a>`
+transfer_repo=transfirió el repositorio <code>%s</code> a <a href="/%s%s">%s</a>
+push_tag=hizo push del tag <a href="%s/%s/src/%s">%s</a> a <a href="%s/%s">%s</a>
+compare_2_commits=Ver la comparación de estos 2 commits
[tool]
-ago=ago
-from_now=from now
-now=now
-1s=1 second %s
-1m=1 minute %s
-1h=1 hour %s
-1d=1 day %s
-1w=1 week %s
-1mon=1 month %s
-1y=1 year %s
-seconds=%d seconds %s
-minutes=%d minutes %s
-hours=%d hours %s
-days=%d days %s
-weeks=%d weeks %s
-months=%d months %s
-years=%d years %s
-raw_seconds=seconds
-raw_minutes=minutes
+ago=hace
+from_now=desde ahora
+now=ahora
+1s=1 segundo %s
+1m=1 minuto %s
+1h=1 hora %s
+1d=1 día %s
+1w=1 semana %s
+1mon=1 mes %s
+1y=1 año %s
+seconds=%d segundos %s
+minutes=%d minutos %s
+hours=%d horas %s
+days=%d días %s
+weeks=%d semanas %s
+months=%d meses %s
+years=%d años %s
+raw_seconds=segundos
+raw_minutes=minutos
diff --git a/conf/locale/locale_fr-CA.ini b/conf/locale/locale_fr-CA.ini index 94a2e968..e744b9d9 100755 --- a/conf/locale/locale_fr-CA.ini +++ b/conf/locale/locale_fr-CA.ini @@ -59,8 +59,8 @@ run_user=Entrer un Utilisateur run_user_helper=L'utilisateur doit avoir accès à la Racine du Référentiel et éxécuter Gogs.
domain=Domaine
domain_helper=Cela affecte les doublons d'URL SSH.
-http_port=HTTP Port
-http_port_helper=Port number which application will listen on.
+http_port=Port HTTP
+http_port_helper=Numéro de port que l'application écoutera.
app_url=URL de l'Application
app_url_helper=Cela affecte les doublons d'URL HTTP/HTTPS et le contenu d'e-mail.
email_title=Paramètres du Service de Messagerie (Facultatif)
@@ -514,10 +514,10 @@ dashboard.delete_repo_archives=Supprimer toutes les archives de référentiels dashboard.delete_repo_archives_success=Toutes les archives de référentiels ont été supprimés avec succès.
dashboard.git_gc_repos=Collecter les déchets des référentiels
dashboard.git_gc_repos_success=Tous les référentiels ont effectué la collecte avec succès.
-dashboard.resync_all_sshkeys=Rewrite '.ssh/autorized_key' file (caution: non-Gogs keys will be lost)
-dashboard.resync_all_sshkeys_success=All public keys have been rewritten successfully.
-dashboard.resync_all_update_hooks=Rewrite all update hook of repositories (needed when custom config path is changed)
-dashboard.resync_all_update_hooks_success=All repositories' update hook have been rewritten successfully.
+dashboard.resync_all_sshkeys=Ré-écrire le fichier '.ssh/autorized_key' (attention : les clés hors-Gogs vont être perdues)
+dashboard.resync_all_sshkeys_success=Toutes les clés publiques ont été ré-écrites avec succès.
+dashboard.resync_all_update_hooks=Ré-écrire tous les hooks de mises à jour des dépôts (requis quand le chemin de la configuration personnalisé est modifié)
+dashboard.resync_all_update_hooks_success=Tous les hooks de mises à jour des dépôts ont été ré-écris avec succès.
dashboard.server_uptime=Durée de Marche Serveur
dashboard.current_goroutine=Goroutines actuelles
@@ -638,7 +638,7 @@ config.db_path_helper=("sqlite3" uniquement) config.service_config=Configuration du Service
config.register_email_confirm=Nécessite une confirmation par courriel
config.disable_register=Désactiver l'Enregistrement
-config.show_registration_button=Show Register Button
+config.show_registration_button=Afficher le bouton d'enregistrement
config.require_sign_in_view=Connexion Obligatoire pour Visualiser
config.mail_notify=Mailer les Notifications
config.enable_cache_avatar=Activer le Cache d'Avatar
@@ -647,6 +647,7 @@ config.reset_password_code_lives=Réinitialiser le Mot De Passe des Limites de C config.webhook_config=Configuration Webhook
config.task_interval=Intervalles de Tâches
config.deliver_timeout=Expiration d'Envoi
+config.skip_tls_verify=Ne pas vérifier TLS
config.mailer_config=Configuration du Maileur
config.mailer_enabled=Activé
config.mailer_name=Nom
diff --git a/conf/locale/locale_ja-JP.ini b/conf/locale/locale_ja-JP.ini index 29ea94e4..441da8fe 100755 --- a/conf/locale/locale_ja-JP.ini +++ b/conf/locale/locale_ja-JP.ini @@ -647,6 +647,7 @@ config.reset_password_code_lives=パスワードリンクの有効期限をリ config.webhook_config=Webhook設定
config.task_interval=タスクの間隔
config.deliver_timeout=送信タイムアウト
+config.skip_tls_verify=TLSの確認を省略
config.mailer_config=メーラーの構成
config.mailer_enabled=有効にした
config.mailer_name=名前
diff --git a/conf/locale/locale_lv-LV.ini b/conf/locale/locale_lv-LV.ini index 6ab81480..b640972c 100755 --- a/conf/locale/locale_lv-LV.ini +++ b/conf/locale/locale_lv-LV.ini @@ -647,6 +647,7 @@ config.reset_password_code_lives=Paroles atiestatīšanas koda ilgums config.webhook_config=Tīkla āķu konfigurācija
config.task_interval=Uzdevuma intervāls
config.deliver_timeout=Piegādes noildze
+config.skip_tls_verify=Skip TLS Verify
config.mailer_config=Sūtītāja konfigurācija
config.mailer_enabled=Iespējots
config.mailer_name=Nosaukums
diff --git a/conf/locale/locale_nl-NL.ini b/conf/locale/locale_nl-NL.ini index adc98c9d..0a4ecb43 100755 --- a/conf/locale/locale_nl-NL.ini +++ b/conf/locale/locale_nl-NL.ini @@ -516,8 +516,8 @@ dashboard.git_gc_repos=Garbage collectie uitvoeren dashboard.git_gc_repos_success=Garbage collectie met succes uitgevoerd.
dashboard.resync_all_sshkeys=Herschrijf '.ssh/authorized_keys' (Let op: alle sleutels die niet van Gogs zijn zullen verloren gaan!)
dashboard.resync_all_sshkeys_success=Alle publieke sleutels zijn herschreven.
-dashboard.resync_all_update_hooks=Rewrite all update hook of repositories (needed when custom config path is changed)
-dashboard.resync_all_update_hooks_success=All repositories' update hook have been rewritten successfully.
+dashboard.resync_all_update_hooks=Herschrijf alle repositorie-hooks (nodig als de configuratie bestandslocatie is gewijzigd)
+dashboard.resync_all_update_hooks_success=Alle repositorie-hooks zijn herschreven.
dashboard.server_uptime=Uptime server
dashboard.current_goroutine=Huidige Goroutines
@@ -647,6 +647,7 @@ config.reset_password_code_lives=Reset wachtwoord Code leven config.webhook_config=Webhook configuratie
config.task_interval=Taakinterval
config.deliver_timeout=Bezorging verlooptijd
+config.skip_tls_verify=TLS certificaat controle overslaan
config.mailer_config=Mailerconfiguatie
config.mailer_enabled=Ingeschakeld
config.mailer_name=Naam
diff --git a/conf/locale/locale_ru-RU.ini b/conf/locale/locale_ru-RU.ini index aa2c0ee5..731ba469 100755 --- a/conf/locale/locale_ru-RU.ini +++ b/conf/locale/locale_ru-RU.ini @@ -58,11 +58,11 @@ repo_path_helper=Всех удаленные репозитории Git буде run_user=Пользователь
run_user_helper=У пользователя должен быть доступ к пути к корню репозитория и к запуску Gogs.
domain=Домен
-domain_helper=This affects SSH clone URLs.
-http_port=HTTP Port
-http_port_helper=Port number which application will listen on.
+domain_helper=Влияет на URL-адреса для клонирования по SSH.
+http_port=Порт HTTP
+http_port_helper=Номер порта, который приложение будет слушать.
app_url=URL приложения
-app_url_helper=This affects HTTP/HTTPS clone URL and somewhere in e-mail.
+app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на адреса в электронной почте.
email_title=Настройки службы электронной почты (опционально)
smtp_host=Узел SMTP
mailer_user=Электронная почта отправителя
@@ -80,7 +80,7 @@ test_git_failed=Не удалось проверить 'git' команду: %v sqlite3_not_available=Ваша версия не поддерживает SQLite3, пожалуйста скачайте официальную бинарную версию от %s, а не версию gobuild.
invalid_db_setting=Настройки базы данных не правильные: %v
invalid_repo_path=Недопустимый путь к корню репозитория: %v
-run_user_not_match=Run user isn't the current user: %s -> %s
+run_user_not_match=Текущий пользователь не является пользователем для запуска: %s -> %s
save_config_failed=Не удалось сохранить конфигурацию: %v
invalid_admin_setting=Указан недопустимый параметр учетной записи администратора: %v
install_success=Добро пожаловать! Мы рады, что вы выбрали Gogs. Веселитесь и берегите себя.
@@ -88,7 +88,7 @@ install_success=Добро пожаловать! Мы рады, что вы вы [home]
uname_holder=Имя пользователь или E-mail
password_holder=Пароль
-switch_dashboard_context=Switch Dashboard Context
+switch_dashboard_context=Переключить контекст панели управления
my_repos=Мои репозитории
collaborative_repos=Совместные репозитории
my_orgs=Моя Организация
@@ -280,9 +280,9 @@ license_helper=Выберите файл лицензии init_readme=Создать репозиторий с файлом README.md
create_repo=Создание репозитория
default_branch=Ветка по умолчанию
-mirror_interval=Mirror Interval (hour)
-goget_meta=Go-Get Meta
-goget_meta_helper=This repository will be <span class="label label-blue label-radius">Go-Getable</span>
+mirror_interval=Интервал зеркалирования (час)
+goget_meta=Meta-тег для go get
+goget_meta_helper=Репозиторий будет доступен для <span class="label label-blue label-radius">go get</span>
need_auth=Требуется авторизация
migrate_type=Тип миграции
@@ -313,9 +313,9 @@ tags=Метки issues=Обсуждения
commits=Коммиты
releases=Релизы
-file_raw=Raw
+file_raw=Исходник
file_history=История
-file_view_raw=View Raw
+file_view_raw=Посмотреть исходник
commits.commits=Коммиты
commits.search=Поиск коммитов
@@ -339,7 +339,7 @@ settings.update_settings=Обновить настройки settings.change_reponame=Имя репозитория изменено
settings.change_reponame_desc=Имя хранилища изменено, вы хотите продолжить? Это действие повлияет на все ссылки, относящиеся к этому репозиторию.
settings.transfer=Передать права собственности
-settings.transfer_desc=Transfer this repo to another user or to an organization where you have admin rights.
+settings.transfer_desc=Передать репозиторий другому пользователю или организации где у вас есть права администратора.
settings.new_owner_has_same_repo=У нового владельца уже есть хранилище с таким названием.
settings.delete=Удалить этот репозиторий
settings.delete_desc=Как только вы удалите репозиторий — пути назад не будет. Удостоверьтесь, что вам это точно нужно.
@@ -352,7 +352,7 @@ settings.confirm_delete=Подтвердить удаление settings.add_collaborator=Добавить нового соавтора
settings.add_collaborator_success=Был добавлен новый соавтор.
settings.remove_collaborator_success=Соавтор был удален.
-settings.user_is_org_member=User is organization member who cannot be added as a collaborator.
+settings.user_is_org_member=Пользователь является членом организации, члены которой не могут быть добавлены в качестве соавтора.
settings.add_webhook=Добавить Webhook
settings.hooks_desc=Webhooks allow external services to be notified when certain events happen on Gogs. When the specified events happen, we'll send a POST request to each of the URLs you provide. Learn more in our <a target="_blank" href="%s">Webhooks Guide</a>.
settings.githooks_desc=Git Hooks are powered by Git itself, you can edit files of supported hooks in the list below to apply custom operations.
@@ -399,12 +399,12 @@ release.ahead=<strong>%d</strong> commits to %s since this release release.source_code=Исходный код
release.tag_name=Имя тега
release.target=Цель
-release.tag_helper=Choose an existing tag, or create a new tag on publish.
+release.tag_helper=Выберите существующий тег, или создайте новый.
release.release_title=Название релиза
release.content_with_md=Содержимое с <a href="%s">Markdown</a>
release.write=Запись
release.preview=Предварительный просмотр
-release.content_placeholder=Write some content
+release.content_placeholder=Напишите что-нибудь
release.loading=Загрузка...
release.prerelease_desc=Это предварительный релиз
release.prerelease_helper=We’ll point out that this release is identified as non-production ready.
@@ -443,10 +443,10 @@ settings.change_orgname_desc=Organization name has been changed, do you want to settings.update_setting_success=Organization setting has been updated successfully.
settings.delete=Удалить Организацию
settings.delete_account=Удалить Эту Организацию
-settings.delete_prompt=The operation will delete this organization permanently, and <strong>CANNOT</strong> be undone!
+settings.delete_prompt=Это действие безвозвратно удалит эту организацию навсегда.
settings.confirm_delete_account=Подтвердить удаление
settings.delete_org_title=Удаление Организации
-settings.delete_org_desc=This organization is going to be deleted permanently, do you want to continue?
+settings.delete_org_desc=Эта организация будет удалена навсегда. Хотите всё-равно продолжить?
settings.hooks_desc=Добавьте автоматическое обновление, который будет вызываться для <strong>всех репозиций</strong> под этой Группой.
members.public=Публичный
@@ -455,7 +455,7 @@ members.private=Приватный members.private_helper=Сделать Публичным
members.owner=Владелец
members.member=Участник
-members.conceal=Conceal
+members.conceal=Скрыть
members.remove=Удалить
members.leave=Покинуть
members.invite_desc=Начните вводить имя пользователя чтобы пригласить нового члена %s:
@@ -494,7 +494,7 @@ organizations=Организации repositories=Репозитории
authentication=Авторизация
config=Настройки
-notices=System Notices
+notices=Системные уведомления
monitor=Мониторинг
prev=Предыдущий.
next=Следующий
@@ -506,15 +506,15 @@ dashboard.statistic_info=В базе данных Gogs записано <b>%d</b dashboard.operation_name=Operation Name
dashboard.operation_switch=Переключить
dashboard.operation_run=Запуск
-dashboard.clean_unbind_oauth=Clean unbound OAuthes
-dashboard.clean_unbind_oauth_success=All unbind OAuthes have been deleted successfully.
+dashboard.clean_unbind_oauth=Удалить не привязанные OAUth
+dashboard.clean_unbind_oauth_success=Не привязанные OAuth аккаунты успешно удалены.
dashboard.delete_inactivate_accounts=Удалить все неактивированные учетные записи
dashboard.delete_inactivate_accounts_success=Все неактивированные учетные записи удалены успешно.
dashboard.delete_repo_archives=Удаление всех архивов репозиториев
dashboard.delete_repo_archives_success=Все архивы репозиториев были успешно удалены.
dashboard.git_gc_repos=Выполнить сборку мусора на репозиториях
dashboard.git_gc_repos_success=Сборка мусора на всех репозиториях успешно выполнена.
-dashboard.resync_all_sshkeys=Rewrite '.ssh/autorized_key' file (caution: non-Gogs keys will be lost)
+dashboard.resync_all_sshkeys=Переписать файл «.ssh/autorized_key» (осторожно: не Gogs ключи будут утеряны)
dashboard.resync_all_sshkeys_success=All public keys have been rewritten successfully.
dashboard.resync_all_update_hooks=Rewrite all update hook of repositories (needed when custom config path is changed)
dashboard.resync_all_update_hooks_success=All repositories' update hook have been rewritten successfully.
@@ -647,6 +647,7 @@ config.reset_password_code_lives=Reset Password Code Lives config.webhook_config=Настройка автоматического обновления репозиции
config.task_interval=Интервал задания
config.deliver_timeout=Задержка доставки
+config.skip_tls_verify=Skip TLS Verify
config.mailer_config=Настройки почты
config.mailer_enabled=Включено
config.mailer_name=Имя
@@ -689,7 +690,7 @@ notices.type=Тип notices.type_1=Репозиторий
notices.desc=Описание
notices.op=Op.
-notices.delete_success=System notice has been deleted successfully.
+notices.delete_success=Системное уведомление успешно удалено.
[action]
create_repo=создан репозиторий <a href="%s/%s"> %s</a>
@@ -698,26 +699,26 @@ create_issue=`opened issue <a href="%s/issues/%s">%[1]s#%[2]s</a>` comment_issue=`commented on issue <a href="%s/issues/%s">%[1]s#%[2]s</a>`
transfer_repo=transfered repository <code>%s</code> to <a href="/%s%s">%s</a>
push_tag=pushed tag <a href="%s/%s/src/%s">%s</a> to <a href="%s/%s">%s</a>
-compare_2_commits=View comparison for these 2 commits
+compare_2_commits=Просмотреть сравнение двух коммитов
[tool]
ago=ago
from_now=from now
now=сейчас
1s=1 second %s
-1m=1 minute %s
-1h=1 hour %s
-1d=1 day %s
-1w=1 week %s
+1m=1 минута %s
+1h=1 час %s
+1d=1 день %s
+1w=1 неделя %s
1mon=1 month %s
-1y=1 year %s
+1y=1 год %s
seconds=%d секунд %s
minutes=%d минут %s
hours=%d часов %s
-days=%d days %s
+days=%d дней %s
weeks=%d weeks %s
months=%d months %s
years=%d years %s
-raw_seconds=seconds
-raw_minutes=minutes
+raw_seconds=секунд
+raw_minutes=минут
diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini index efeca15a..35370682 100755 --- a/conf/locale/locale_zh-CN.ini +++ b/conf/locale/locale_zh-CN.ini @@ -647,6 +647,7 @@ config.reset_password_code_lives=重置密码链接有效期 config.webhook_config=Web 钩子配置
config.task_interval=任务周期
config.deliver_timeout=推送超时
+config.skip_tls_verify=忽略 TLS 验证
config.mailer_config=邮件配置
config.mailer_enabled=启用服务
config.mailer_name=发送者名称
diff --git a/conf/locale/locale_zh-HK.ini b/conf/locale/locale_zh-HK.ini index 0c9ba190..a9b51a5b 100755 --- a/conf/locale/locale_zh-HK.ini +++ b/conf/locale/locale_zh-HK.ini @@ -647,6 +647,7 @@ config.reset_password_code_lives=重置密碼連結有效期 config.webhook_config=Web 鉤子配置
config.task_interval=任務周期
config.deliver_timeout=推送超時
+config.skip_tls_verify=Skip TLS Verify
config.mailer_config=郵件配置
config.mailer_enabled=啟用服務
config.mailer_name=發送者名稱
diff --git a/docker/blocks/docker_gogs/Dockerfile b/docker/blocks/docker_gogs/Dockerfile index 2c98cc50..efedc31a 100644 --- a/docker/blocks/docker_gogs/Dockerfile +++ b/docker/blocks/docker_gogs/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:14.04 RUN apt-get update && apt-get install -y \ build-essential ca-certificates curl \ - bzr git mercurial \ + bzr git mercurial openssh-client\ --no-install-recommends ENV GOLANG_VERSION 1.3 diff --git a/docker/blocks/docker_gogs_dev/Dockerfile b/docker/blocks/docker_gogs_dev/Dockerfile index 2a628c2d..ce653fbe 100644 --- a/docker/blocks/docker_gogs_dev/Dockerfile +++ b/docker/blocks/docker_gogs_dev/Dockerfile @@ -5,7 +5,7 @@ FROM ubuntu:14.04 RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ apt-get install -qy \ build-essential ca-certificates curl \ - bzr git mercurial \ + bzr git mercurial openssh-client\ --no-install-recommends ENV GOLANG_VERSION 1.3 diff --git a/docker/templates/init_gogs.sh.tpl b/docker/templates/init_gogs.sh.tpl index 26cff4e5..ada11f95 100644 --- a/docker/templates/init_gogs.sh.tpl +++ b/docker/templates/init_gogs.sh.tpl @@ -1,6 +1,6 @@ #!/bin/sh -if [ ! -d "$DIRECTORY" ]; then +if [ ! -d "$GOGS_CUSTOM_CONF_PATH" ]; then mkdir -p $GOGS_CUSTOM_CONF_PATH echo " @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.5.13.0210 Beta" +const APP_VER = "0.5.16.0228 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/models/access.go b/models/access.go index 81aa43dc..f353c39a 100644 --- a/models/access.go +++ b/models/access.go @@ -5,76 +5,190 @@ package models import ( - "strings" - "time" - - "github.com/go-xorm/xorm" + "fmt" ) -type AccessType int +type AccessMode int const ( - READABLE AccessType = iota + 1 - WRITABLE + ACCESS_MODE_NONE AccessMode = iota + ACCESS_MODE_READ + ACCESS_MODE_WRITE + ACCESS_MODE_ADMIN + ACCESS_MODE_OWNER ) -// Access represents the accessibility of user to repository. +// Access represents the highest access level of a user to the repository. The only access type +// that is not in this table is the real owner of a repository. In case of an organization +// repository, the members of the owners team are in this table. type Access struct { - Id int64 - UserName string `xorm:"UNIQUE(s)"` - RepoName string `xorm:"UNIQUE(s)"` // <user name>/<repo name> - Mode AccessType `xorm:"UNIQUE(s)"` - Created time.Time `xorm:"CREATED"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + Mode AccessMode +} + +func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) { + mode := ACCESS_MODE_NONE + if !repo.IsPrivate { + mode = ACCESS_MODE_READ + } + + if u != nil { + if u.Id == repo.OwnerId { + return ACCESS_MODE_OWNER, nil + } + + a := &Access{UserID: u.Id, RepoID: repo.Id} + if has, err := e.Get(a); !has || err != nil { + return mode, err + } + return a.Mode, nil + } + + return mode, nil } -// AddAccess adds new access record. -func AddAccess(access *Access) error { - access.UserName = strings.ToLower(access.UserName) - access.RepoName = strings.ToLower(access.RepoName) - _, err := x.Insert(access) - return err +// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the +// user does not have access. User can be nil! +func AccessLevel(u *User, repo *Repository) (AccessMode, error) { + return accessLevel(x, u, repo) } -// UpdateAccess updates access information. -func UpdateAccess(access *Access) error { - access.UserName = strings.ToLower(access.UserName) - access.RepoName = strings.ToLower(access.RepoName) - _, err := x.Id(access.Id).Update(access) - return err +func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) { + mode, err := accessLevel(e, u, repo) + return testMode <= mode, err } -// DeleteAccess deletes access record. -func DeleteAccess(access *Access) error { - _, err := x.Delete(access) - return err +// HasAccess returns true if someone has the request access level. User can be nil! +func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) { + return hasAccess(x, u, repo, testMode) } -// UpdateAccess updates access information with session for rolling back. -func UpdateAccessWithSession(sess *xorm.Session, access *Access) error { - if _, err := sess.Id(access.Id).Update(access); err != nil { - sess.Rollback() - return err +// GetAccessibleRepositories finds all repositories where a user has access to, +// besides his own. +func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) { + accesses := make([]*Access, 0, 10) + if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil { + return nil, err } - return nil + + repos := make(map[*Repository]AccessMode, len(accesses)) + for _, access := range accesses { + repo, err := GetRepositoryById(access.RepoID) + if err != nil { + return nil, err + } + if err = repo.GetOwner(); err != nil { + return nil, err + } else if repo.OwnerId == u.Id { + continue + } + repos[repo] = access.Mode + } + + // FIXME: should we generate an ordered list here? Random looks weird. + return repos, nil +} + +func maxAccessMode(modes ...AccessMode) AccessMode { + max := ACCESS_MODE_NONE + for _, mode := range modes { + if mode > max { + max = mode + } + } + return max } -// HasAccess returns true if someone can read or write to given repository. -// The repoName should be in format <username>/<reponame>. -func HasAccess(uname, repoName string, mode AccessType) (bool, error) { - if len(repoName) == 0 { - return false, nil +// FIXME: do corss-comparison so reduce deletions and additions to the minimum? +func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) { + minMode := ACCESS_MODE_READ + if !repo.IsPrivate { + minMode = ACCESS_MODE_WRITE } - access := &Access{ - UserName: strings.ToLower(uname), - RepoName: strings.ToLower(repoName), + + newAccesses := make([]Access, 0, len(accessMap)) + for userID, mode := range accessMap { + if mode < minMode { + continue + } + newAccesses = append(newAccesses, Access{ + UserID: userID, + RepoID: repo.Id, + Mode: mode, + }) } - has, err := x.Get(access) + + // Delete old accesses and insert new ones for repository. + if _, err = e.Delete(&Access{RepoID: repo.Id}); err != nil { + return fmt.Errorf("delete old accesses: %v", err) + } else if _, err = e.Insert(newAccesses); err != nil { + return fmt.Errorf("insert new accesses: %v", err) + } + return nil +} + +// FIXME: should be able to have read-only access. +// Give all collaborators write access. +func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { + collaborators, err := repo.getCollaborators(e) if err != nil { - return false, err - } else if !has { - return false, nil - } else if mode > access.Mode { - return false, nil + return fmt.Errorf("getCollaborators: %v", err) + } + for _, c := range collaborators { + accessMap[c.Id] = ACCESS_MODE_WRITE + } + return nil +} + +// recalculateTeamAccesses recalculates new accesses for teams of an organization +// except the team whose ID is given. It is used to assign a team ID when +// remove repository from that team. +func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) { + accessMap := make(map[int64]AccessMode, 20) + + if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil { + return fmt.Errorf("refreshCollaboratorAccesses: %v", err) + } + + if err = repo.getOwner(e); err != nil { + return err + } + if repo.Owner.IsOrganization() { + if err = repo.Owner.getTeams(e); err != nil { + return err + } + + for _, t := range repo.Owner.Teams { + if t.ID == ignTeamID { + continue + } + if t.IsOwnerTeam() { + t.Authorize = ACCESS_MODE_OWNER + } + + if err = t.getMembers(e); err != nil { + return fmt.Errorf("getMembers '%d': %v", t.ID, err) + } + for _, m := range t.Members { + accessMap[m.Id] = maxAccessMode(accessMap[m.Id], t.Authorize) + } + } + } + + return repo.refreshAccesses(e, accessMap) +} + +func (repo *Repository) recalculateAccesses(e Engine) error { + accessMap := make(map[int64]AccessMode, 20) + if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil { + return fmt.Errorf("refreshCollaboratorAccesses: %v", err) } - return true, nil + return repo.refreshAccesses(e, accessMap) +} + +// RecalculateAccesses recalculates all accesses for repository. +func (r *Repository) RecalculateAccesses() error { + return r.recalculateAccesses(x) } diff --git a/models/action.go b/models/action.go index 5cba2f51..f872104e 100644 --- a/models/action.go +++ b/models/action.go @@ -182,6 +182,17 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com } issue.IsClosed = true + if err = issue.GetLabels(); err != nil { + return err + } + for _, label := range issue.Labels { + label.NumClosedIssues++ + + if err = UpdateLabel(label); err != nil { + return err + } + } + if err = UpdateIssue(issue); err != nil { return err } else if err = UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil { @@ -230,6 +241,17 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com } issue.IsClosed = false + if err = issue.GetLabels(); err != nil { + return err + } + for _, label := range issue.Labels { + label.NumClosedIssues-- + + if err = UpdateLabel(label); err != nil { + return err + } + } + if err = UpdateIssue(issue); err != nil { return err } else if err = UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil { @@ -412,46 +434,58 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, return nil } -// NewRepoAction adds new action for creating repository. -func NewRepoAction(u *User, repo *Repository) (err error) { - if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email, - OpType: CREATE_REPO, RepoId: repo.Id, RepoUserName: repo.Owner.Name, RepoName: repo.Name, - IsPrivate: repo.IsPrivate}); err != nil { - log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name) - return err +func newRepoAction(e Engine, u *User, repo *Repository) (err error) { + if err = notifyWatchers(e, &Action{ + ActUserId: u.Id, + ActUserName: u.Name, + ActEmail: u.Email, + OpType: CREATE_REPO, + RepoId: repo.Id, + RepoUserName: repo.Owner.Name, + RepoName: repo.Name, + IsPrivate: repo.IsPrivate}); err != nil { + return fmt.Errorf("notify watchers '%d/%s'", u.Id, repo.Id) } log.Trace("action.NewRepoAction: %s/%s", u.Name, repo.Name) return err } -// TransferRepoAction adds new action for transferring repository. -func TransferRepoAction(u, newUser *User, repo *Repository) (err error) { +// NewRepoAction adds new action for creating repository. +func NewRepoAction(u *User, repo *Repository) (err error) { + return newRepoAction(x, u, repo) +} + +func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repository) (err error) { action := &Action{ - ActUserId: u.Id, - ActUserName: u.Name, - ActEmail: u.Email, + ActUserId: actUser.Id, + ActUserName: actUser.Name, + ActEmail: actUser.Email, OpType: TRANSFER_REPO, RepoId: repo.Id, - RepoUserName: newUser.Name, + RepoUserName: newOwner.Name, RepoName: repo.Name, IsPrivate: repo.IsPrivate, - Content: path.Join(repo.Owner.LowerName, repo.LowerName), + Content: path.Join(oldOwner.LowerName, repo.LowerName), } - if err = NotifyWatchers(action); err != nil { - log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name) - return err + if err = notifyWatchers(e, action); err != nil { + return fmt.Errorf("notify watchers '%d/%s'", actUser.Id, repo.Id) } // Remove watch for organization. if repo.Owner.IsOrganization() { - if err = WatchRepo(repo.Owner.Id, repo.Id, false); err != nil { - log.Error(4, "WatchRepo", err) + if err = watchRepo(e, repo.Owner.Id, repo.Id, false); err != nil { + return fmt.Errorf("watch repository: %v", err) } } - log.Trace("action.TransferRepoAction: %s/%s", u.Name, repo.Name) - return err + log.Trace("action.TransferRepoAction: %s/%s", actUser.Name, repo.Name) + return nil +} + +// TransferRepoAction adds new action for transferring repository. +func TransferRepoAction(actUser, oldOwner, newOwner *User, repo *Repository) (err error) { + return transferRepoAction(x, actUser, oldOwner, newOwner, repo) } // GetFeeds returns action list of given user in given context. diff --git a/models/issue.go b/models/issue.go index d9a24063..2b80e576 100644 --- a/models/issue.go +++ b/models/issue.go @@ -282,30 +282,33 @@ type IssueUser struct { } // NewIssueUserPairs adds new issue-user pairs for new issue of repository. -func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err error) { - iu := &IssueUser{IssueId: iid, RepoId: rid} - - us, err := GetCollaborators(repoName) +func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID int64) (err error) { + users, err := repo.GetCollaborators() if err != nil { return err } + iu := &IssueUser{ + IssueId: issueID, + RepoId: repo.Id, + } + isNeedAddPoster := true - for _, u := range us { + for _, u := range users { iu.Uid = u.Id - iu.IsPoster = iu.Uid == pid + iu.IsPoster = iu.Uid == posterID if isNeedAddPoster && iu.IsPoster { isNeedAddPoster = false } - iu.IsAssigned = iu.Uid == aid + iu.IsAssigned = iu.Uid == assigneeID if _, err = x.Insert(iu); err != nil { return err } } if isNeedAddPoster { - iu.Uid = pid + iu.Uid = posterID iu.IsPoster = true - iu.IsAssigned = iu.Uid == aid + iu.IsAssigned = iu.Uid == assigneeID if _, err = x.Insert(iu); err != nil { return err } @@ -561,7 +564,7 @@ func GetLabels(repoId int64) ([]*Label, error) { // UpdateLabel updates label information. func UpdateLabel(l *Label) error { - _, err := x.Id(l.Id).Update(l) + _, err := x.Id(l.Id).AllCols().Update(l) return err } diff --git a/models/login.go b/models/login.go index 125e110a..1dc1b6ca 100644 --- a/models/login.go +++ b/models/login.go @@ -231,7 +231,7 @@ func UserSignIn(uname, passwd string) (*User, error) { // Return the same LoginUserPlain semantic // FIXME: https://github.com/gogits/gogs/issues/672 func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) { - mail, logged := cfg.Ldapsource.SearchEntry(name, passwd) + name, fn, sn, mail, logged := cfg.Ldapsource.SearchEntry(name, passwd) if !logged { // User not in LDAP, do nothing return nil, ErrUserNotExist @@ -247,6 +247,7 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP u = &User{ Name: name, + FullName: fn + " " + sn, LoginType: LDAP, LoginSource: sourceId, LoginName: name, diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 3586e5d0..25c130a9 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -1,12 +1,44 @@ +// Copyright 2015 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 migrations import ( - "errors" + "fmt" + "strings" + "time" + "github.com/Unknwon/com" "github.com/go-xorm/xorm" + + "github.com/gogits/gogs/modules/log" + "github.com/gogits/gogs/modules/setting" ) -type migration func(*xorm.Engine) error +const _MIN_DB_VER = 0 + +type Migration interface { + Description() string + Migrate(*xorm.Engine) error +} + +type migration struct { + description string + migrate func(*xorm.Engine) error +} + +func NewMigration(desc string, fn func(*xorm.Engine) error) Migration { + return &migration{desc, fn} +} + +func (m *migration) Description() string { + return m.description +} + +func (m *migration) Migrate(x *xorm.Engine) error { + return m.migrate(x) +} // The version table. Should have only one row with id==1 type Version struct { @@ -15,30 +47,55 @@ type Version struct { } // This is a sequence of migrations. Add new migrations to the bottom of the list. -// If you want to "retire" a migration, replace it with "expiredMigration" -var migrations = []migration{} +// If you want to "retire" a migration, remove it from the top of the list and +// update _MIN_VER_DB accordingly +var migrations = []Migration{ + NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1 + NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2 + NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3 + NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4 +} // Migrate database to current version func Migrate(x *xorm.Engine) error { if err := x.Sync(new(Version)); err != nil { - return err + return fmt.Errorf("sync: %v", err) } currentVersion := &Version{Id: 1} has, err := x.Get(currentVersion) if err != nil { - return err + return fmt.Errorf("get: %v", err) } else if !has { - if _, err = x.InsertOne(currentVersion); err != nil { + // If the user table does not exist it is a fresh installation and we + // can skip all migrations. + needsMigration, err := x.IsTableExist("user") + if err != nil { return err } + if needsMigration { + isEmpty, err := x.IsTableEmpty("user") + if err != nil { + return err + } + // If the user table is empty it is a fresh installation and we can + // skip all migrations. + needsMigration = !isEmpty + } + if !needsMigration { + currentVersion.Version = int64(_MIN_DB_VER + len(migrations)) + } + + if _, err = x.InsertOne(currentVersion); err != nil { + return fmt.Errorf("insert: %v", err) + } } v := currentVersion.Version - - for i, migration := range migrations[v:] { - if err = migration(x); err != nil { - return err + for i, m := range migrations[v-_MIN_DB_VER:] { + log.Info("Migration: %s", m.Description()) + if err = m.Migrate(x); err != nil { + return fmt.Errorf("do migrate: %v", err) } currentVersion.Version = v + int64(i) + 1 if _, err = x.Id(1).Update(currentVersion); err != nil { @@ -48,6 +105,267 @@ func Migrate(x *xorm.Engine) error { return nil } -func expiredMigration(x *xorm.Engine) error { - return errors.New("You are migrating from a too old gogs version") +func sessionRelease(sess *xorm.Session) { + if !sess.IsCommitedOrRollbacked { + sess.Rollback() + } + sess.Close() +} + +func accessToCollaboration(x *xorm.Engine) (err error) { + type Collaboration struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + Created time.Time + } + + if err = x.Sync(new(Collaboration)); err != nil { + return fmt.Errorf("sync: %v", err) + } + + results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name") + if err != nil { + return err + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + offset := strings.Split(time.Now().String(), " ")[2] + for _, result := range results { + mode := com.StrTo(result["mode"]).MustInt64() + // Collaborators must have write access. + if mode < 2 { + continue + } + + userID := com.StrTo(result["uid"]).MustInt64() + repoRefName := string(result["repo"]) + + var created time.Time + switch { + case setting.UseSQLite3: + created, _ = time.Parse(time.RFC3339, string(result["created"])) + case setting.UseMySQL: + created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset) + case setting.UsePostgreSQL: + created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset) + } + + // find owner of repository + parts := strings.SplitN(repoRefName, "/", 2) + ownerName := parts[0] + repoName := parts[1] + + results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName) + if err != nil { + return err + } + if len(results) < 1 { + continue + } + + ownerID := com.StrTo(results[0]["uid"]).MustInt64() + if ownerID == userID { + continue + } + + // test if user is member of owning organization + isMember := false + for _, member := range results { + memberID := com.StrTo(member["memberid"]).MustInt64() + // We can skip all cases that a user is member of the owning organization + if memberID == userID { + isMember = true + } + } + if isMember { + continue + } + + results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName) + if err != nil { + return err + } else if len(results) < 1 { + continue + } + + collaboration := &Collaboration{ + UserID: userID, + RepoID: com.StrTo(results[0]["id"]).MustInt64(), + } + has, err := sess.Get(collaboration) + if err != nil { + return err + } else if has { + continue + } + + collaboration.Created = created + if _, err = sess.InsertOne(collaboration); err != nil { + return err + } + } + + return sess.Commit() +} + +func ownerTeamUpdate(x *xorm.Engine) (err error) { + if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil { + return fmt.Errorf("update owner team table: %v", err) + } + return nil +} + +func accessRefactor(x *xorm.Engine) (err error) { + type ( + AccessMode int + Access struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + Mode AccessMode + } + UserRepo struct { + UserID int64 + RepoID int64 + } + ) + + // We consiously don't start a session yet as we make only reads for now, no writes + + accessMap := make(map[UserRepo]AccessMode, 50) + + results, err := x.Query("SELECT r.id AS `repo_id`, r.is_private AS `is_private`, r.owner_id AS `owner_id`, u.type AS `owner_type` FROM `repository` r LEFT JOIN `user` u ON r.owner_id=u.id") + if err != nil { + return fmt.Errorf("select repositories: %v", err) + } + for _, repo := range results { + repoID := com.StrTo(repo["repo_id"]).MustInt64() + isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0 + ownerID := com.StrTo(repo["owner_id"]).MustInt64() + ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0 + + results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID) + if err != nil { + return fmt.Errorf("select collaborators: %v", err) + } + for _, user := range results { + userID := com.StrTo(user["user_id"]).MustInt64() + accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS + } + + if !ownerIsOrganization { + continue + } + + // The minimum level to add a new access record, + // because public repository has implicit open access. + minAccessLevel := AccessMode(0) + if !isPrivate { + minAccessLevel = 1 + } + + repoString := "$" + string(repo["repo_id"]) + "|" + + results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel)) + if err != nil { + return fmt.Errorf("select teams from org: %v", err) + } + + for _, team := range results { + if !strings.Contains(string(team["repo_ids"]), repoString) { + continue + } + teamID := com.StrTo(team["id"]).MustInt64() + mode := AccessMode(com.StrTo(team["authorize"]).MustInt()) + + results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID) + if err != nil { + return fmt.Errorf("select users from team: %v", err) + } + for _, user := range results { + userID := com.StrTo(user["user_id"]).MustInt64() + accessMap[UserRepo{userID, repoID}] = mode + } + } + } + + // Drop table can't be in a session (at least not in sqlite) + if _, err = x.Exec("DROP TABLE `access`"); err != nil { + return fmt.Errorf("drop access table: %v", err) + } + + // Now we start writing so we make a session + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if err = sess.Sync2(new(Access)); err != nil { + return fmt.Errorf("sync: %v", err) + } + + accesses := make([]*Access, 0, len(accessMap)) + for ur, mode := range accessMap { + accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode}) + } + + if _, err = sess.Insert(accesses); err != nil { + return fmt.Errorf("insert accesses: %v", err) + } + + return sess.Commit() +} + +func teamToTeamRepo(x *xorm.Engine) error { + type TeamRepo struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + } + + teamRepos := make([]*TeamRepo, 0, 50) + + results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`") + if err != nil { + return fmt.Errorf("select teams: %v", err) + } + for _, team := range results { + orgID := com.StrTo(team["org_id"]).MustInt64() + teamID := com.StrTo(team["id"]).MustInt64() + + for _, idStr := range strings.Split(string(team["repo_ids"]), "|") { + repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64() + if repoID == 0 { + continue + } + + teamRepos = append(teamRepos, &TeamRepo{ + OrgID: orgID, + TeamID: teamID, + RepoID: repoID, + }) + } + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if err = sess.Sync2(new(TeamRepo)); err != nil { + return fmt.Errorf("sync: %v", err) + } else if _, err = sess.Insert(teamRepos); err != nil { + return fmt.Errorf("insert team-repos: %v", err) + } + + return sess.Commit() } diff --git a/models/models.go b/models/models.go index 3eeeabda..a9436fca 100644 --- a/models/models.go +++ b/models/models.go @@ -12,6 +12,7 @@ import ( "strings" _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/core" "github.com/go-xorm/xorm" _ "github.com/lib/pq" @@ -23,12 +24,22 @@ import ( type Engine interface { Delete(interface{}) (int64, error) Exec(string, ...interface{}) (sql.Result, error) + Find(interface{}, ...interface{}) error Get(interface{}) (bool, error) Insert(...interface{}) (int64, error) + InsertOne(interface{}) (int64, error) Id(interface{}) *xorm.Session + Sql(string, ...interface{}) *xorm.Session Where(string, ...interface{}) *xorm.Session } +func sessionRelease(sess *xorm.Session) { + if !sess.IsCommitedOrRollbacked { + sess.Rollback() + } + sess.Close() +} + var ( x *xorm.Engine tables []interface{} @@ -39,24 +50,30 @@ var ( } EnableSQLite3 bool - UseSQLite3 bool ) func init() { tables = append(tables, - new(User), new(PublicKey), new(Follow), new(Oauth2), new(AccessToken), - new(Repository), new(Watch), new(Star), new(Action), new(Access), + new(User), new(PublicKey), new(Oauth2), new(AccessToken), + new(Repository), new(Collaboration), new(Access), + new(Watch), new(Star), new(Follow), new(Action), new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone), new(Mirror), new(Release), new(LoginSource), new(Webhook), - new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser), + new(UpdateTask), new(HookTask), + new(Team), new(OrgUser), new(TeamUser), new(TeamRepo), new(Notice), new(EmailAddress)) } func LoadModelsConfig() { sec := setting.Cfg.Section("database") DbCfg.Type = sec.Key("DB_TYPE").String() - if DbCfg.Type == "sqlite3" { - UseSQLite3 = true + switch DbCfg.Type { + case "sqlite3": + setting.UseSQLite3 = true + case "mysql": + setting.UseMySQL = true + case "postgres": + setting.UsePostgreSQL = true } DbCfg.Host = sec.Key("HOST").String() DbCfg.Name = sec.Key("NAME").String() @@ -103,6 +120,7 @@ func NewTestEngine(x *xorm.Engine) (err error) { return fmt.Errorf("connect to database: %v", err) } + x.SetMapper(core.GonicMapper{}) return x.Sync(tables...) } @@ -112,6 +130,8 @@ func SetEngine() (err error) { return fmt.Errorf("connect to database: %v", err) } + x.SetMapper(core.GonicMapper{}) + // WARNING: for serv command, MUST remove the output to os.stdout, // so use log file to instead print to stdout. logPath := path.Join(setting.LogRootPath, "xorm.log") @@ -121,7 +141,7 @@ func SetEngine() (err error) { if err != nil { return fmt.Errorf("models.init(fail to create xorm.log): %v", err) } - x.Logger = xorm.NewSimpleLogger(f) + x.SetLogger(xorm.NewSimpleLogger(f)) x.ShowSQL = true x.ShowInfo = true @@ -137,12 +157,13 @@ func NewEngine() (err error) { } if err = migrations.Migrate(x); err != nil { - return err + return fmt.Errorf("migrate: %v", err) } if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { return fmt.Errorf("sync database struct error: %v\n", err) } + return nil } diff --git a/models/org.go b/models/org.go index 3d37a37d..2c2b9b63 100644 --- a/models/org.go +++ b/models/org.go @@ -8,11 +8,8 @@ import ( "errors" "fmt" "os" - "path" "strings" - "github.com/Unknwon/com" - "github.com/gogits/gogs/modules/base" ) @@ -31,22 +28,34 @@ func (org *User) IsOwnedBy(uid int64) bool { // IsOrgMember returns true if given user is member of organization. func (org *User) IsOrgMember(uid int64) bool { - return IsOrganizationMember(org.Id, uid) + return org.IsOrganization() && IsOrganizationMember(org.Id, uid) +} + +func (org *User) getTeam(e Engine, name string) (*Team, error) { + return getTeam(e, org.Id, name) } // GetTeam returns named team of organization. func (org *User) GetTeam(name string) (*Team, error) { - return GetTeam(org.Id, name) + return org.getTeam(x, name) +} + +func (org *User) getOwnerTeam(e Engine) (*Team, error) { + return org.getTeam(e, OWNER_TEAM) } // GetOwnerTeam returns owner team of organization. func (org *User) GetOwnerTeam() (*Team, error) { - return org.GetTeam(OWNER_TEAM) + return org.getOwnerTeam(x) +} + +func (org *User) getTeams(e Engine) error { + return e.Where("org_id=?", org.Id).Find(&org.Teams) } // GetTeams returns all teams that belong to organization. func (org *User) GetTeams() error { - return x.Where("org_id=?", org.Id).Find(&org.Teams) + return org.getTeams(x) } // GetMembers returns all members of organization. @@ -76,6 +85,15 @@ func (org *User) RemoveMember(uid int64) error { return RemoveOrgUser(org.Id, uid) } +func (org *User) removeOrgRepo(e Engine, repoID int64) error { + return removeOrgRepo(e, org.Id, repoID) +} + +// RemoveOrgRepo removes all team-repository relations of organization. +func (org *User) RemoveOrgRepo(repoID int64) error { + return org.removeOrgRepo(x, repoID) +} + // IsOrgEmailUsed returns true if the e-mail has been used in organization account. func IsOrgEmailUsed(email string) (bool, error) { if len(email) == 0 { @@ -93,7 +111,7 @@ func CreateOrganization(org, owner *User) (*User, error) { return nil, ErrUserNameIllegal } - isExist, err := IsUserExist(org.Name) + isExist, err := IsUserExist(0, org.Name) if err != nil { return nil, err } else if isExist { @@ -116,53 +134,48 @@ func CreateOrganization(org, owner *User) (*User, error) { org.NumMembers = 1 sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { return nil, err } if _, err = sess.Insert(org); err != nil { - sess.Rollback() - return nil, err - } - - if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil { - sess.Rollback() return nil, err } // Create default owner team. t := &Team{ - OrgId: org.Id, + OrgID: org.Id, LowerName: strings.ToLower(OWNER_TEAM), Name: OWNER_TEAM, - Authorize: ORG_ADMIN, + Authorize: ACCESS_MODE_OWNER, 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, + OrgID: org.Id, IsOwner: true, NumTeams: 1, } if _, err = sess.Insert(ou); err != nil { - sess.Rollback() return nil, err } tu := &TeamUser{ Uid: owner.Id, - OrgId: org.Id, - TeamId: t.Id, + OrgID: org.Id, + TeamID: t.ID, } if _, err = sess.Insert(tu); err != nil { - sess.Rollback() + return nil, err + } + + if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil { return nil, err } @@ -213,15 +226,15 @@ func DeleteOrganization(org *User) (err error) { return err } - if _, err = sess.Delete(&Team{OrgId: org.Id}); err != nil { + if _, err = sess.Delete(&Team{OrgID: org.Id}); err != nil { sess.Rollback() return err } - if _, err = sess.Delete(&OrgUser{OrgId: org.Id}); err != nil { + if _, err = sess.Delete(&OrgUser{OrgID: org.Id}); err != nil { sess.Rollback() return err } - if _, err = sess.Delete(&TeamUser{OrgId: org.Id}); err != nil { + if _, err = sess.Delete(&TeamUser{OrgID: org.Id}); err != nil { sess.Rollback() return err } @@ -237,9 +250,9 @@ func DeleteOrganization(org *User) (err error) { // OrgUser represents an organization-user relation. type OrgUser struct { - Id int64 + ID int64 `xorm:"pk autoincr"` Uid int64 `xorm:"INDEX UNIQUE(s)"` - OrgId int64 `xorm:"INDEX UNIQUE(s)"` + OrgID int64 `xorm:"INDEX UNIQUE(s)"` IsPublic bool IsOwner bool NumTeams int @@ -288,7 +301,7 @@ func ChangeOrgUserStatus(orgId, uid int64, public bool) error { } ou.IsPublic = public - _, err = x.Id(ou.Id).AllCols().Update(ou) + _, err = x.Id(ou.ID).AllCols().Update(ou) return err } @@ -306,7 +319,7 @@ func AddOrgUser(orgId, uid int64) error { ou := &OrgUser{ Uid: uid, - OrgId: orgId, + OrgID: orgId, } if _, err := sess.Insert(ou); err != nil { @@ -357,10 +370,10 @@ func RemoveOrgUser(orgId, uid int64) error { return err } - if _, err := sess.Id(ou.Id).Delete(ou); err != nil { + if _, err := sess.Id(ou.ID).Delete(ou); err != nil { sess.Rollback() return err - } else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members - 1 WHERE id = ?", orgId); err != nil { + } else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id = ?", orgId); err != nil { sess.Rollback() return err } @@ -371,10 +384,10 @@ func RemoveOrgUser(orgId, uid int64) error { return err } access := &Access{ - UserName: u.LowerName, + UserID: u.Id, } for _, repo := range org.Repos { - access.RepoName = path.Join(org.LowerName, repo.LowerName) + access.RepoID = repo.Id if _, err = sess.Delete(access); err != nil { sess.Rollback() return err @@ -390,7 +403,7 @@ func RemoveOrgUser(orgId, uid int64) error { return err } for _, t := range ts { - if err = removeTeamMember(sess, org.Id, t.Id, u.Id); err != nil { + if err = removeTeamMember(sess, org.Id, t.ID, u.Id); err != nil { return err } } @@ -405,32 +418,16 @@ func RemoveOrgUser(orgId, uid int64) error { // |____| \___ >____ /__|_| / // \/ \/ \/ -type AuthorizeType int - -const ( - ORG_READABLE AuthorizeType = iota + 1 - ORG_WRITABLE - ORG_ADMIN -) - -func AuthorizeToAccessType(auth AuthorizeType) AccessType { - if auth == ORG_READABLE { - return READABLE - } - return WRITABLE -} - const OWNER_TEAM = "Owners" // Team represents a organization team. type Team struct { - Id int64 - OrgId int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` LowerName string Name string Description string - Authorize AuthorizeType - RepoIds string `xorm:"TEXT"` + Authorize AccessMode Repos []*Repository `xorm:"-"` Members []*User `xorm:"-"` NumRepos int @@ -444,60 +441,80 @@ func (t *Team) IsOwnerTeam() bool { // IsTeamMember returns true if given user is a member of team. func (t *Team) IsMember(uid int64) bool { - return IsTeamMember(t.OrgId, t.Id, uid) + return IsTeamMember(t.OrgID, t.ID, uid) } -// GetRepositories returns all repositories in team of organization. -func (t *Team) GetRepositories() error { - idStrs := strings.Split(t.RepoIds, "|") - t.Repos = make([]*Repository, 0, len(idStrs)) - for _, str := range idStrs { - if len(str) == 0 { - continue - } - id := com.StrTo(str[1:]).MustInt64() - if id == 0 { - continue - } - repo, err := GetRepositoryById(id) +func (t *Team) getRepositories(e Engine) (err error) { + teamRepos := make([]*TeamRepo, 0, t.NumRepos) + if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil { + return fmt.Errorf("get team-repos: %v", err) + } + + t.Repos = make([]*Repository, 0, len(teamRepos)) + for i := range teamRepos { + repo, err := getRepositoryById(e, teamRepos[i].RepoID) if err != nil { - return err + return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err) } t.Repos = append(t.Repos, repo) } return nil } +// GetRepositories returns all repositories in team of organization. +func (t *Team) GetRepositories() error { + return t.getRepositories(x) +} + +func (t *Team) getMembers(e Engine) (err error) { + t.Members, err = getTeamMembers(e, t.ID) + return err +} + // GetMembers returns all members in team of organization. func (t *Team) GetMembers() (err error) { - t.Members, err = GetTeamMembers(t.OrgId, t.Id) - return err + return t.getMembers(x) } // AddMember adds new member to team of organization. func (t *Team) AddMember(uid int64) error { - return AddTeamMember(t.OrgId, t.Id, uid) + return AddTeamMember(t.OrgID, t.ID, uid) } // RemoveMember removes member from team of organization. func (t *Team) RemoveMember(uid int64) error { - return RemoveTeamMember(t.OrgId, t.Id, uid) + return RemoveTeamMember(t.OrgID, t.ID, uid) } -// addAccessWithAuthorize inserts or updates access with given mode. -func addAccessWithAuthorize(e Engine, access *Access, mode AccessType) error { - has, err := e.Get(access) - if err != nil { - return fmt.Errorf("fail to get access: %v", err) +func (t *Team) hasRepository(e Engine, repoID int64) bool { + return hasTeamRepo(e, t.OrgID, t.ID, repoID) +} + +// HasRepository returns true if given repository belong to team. +func (t *Team) HasRepository(repoID int64) bool { + return HasTeamRepo(t.OrgID, t.ID, repoID) +} + +func (t *Team) addRepository(e Engine, repo *Repository) (err error) { + if err = addTeamRepo(e, t.OrgID, t.ID, repo.Id); err != nil { + return err } - access.Mode = mode - if has { - if _, err = e.Id(access.Id).Update(access); err != nil { - return fmt.Errorf("fail to update access: %v", err) - } - } else { - if _, err = e.Insert(access); err != nil { - return fmt.Errorf("fail to insert access: %v", err) + + t.NumRepos++ + if _, err = e.Id(t.ID).AllCols().Update(t); err != nil { + return fmt.Errorf("update team: %v", err) + } + + if err = repo.recalculateTeamAccesses(e, 0); err != nil { + return fmt.Errorf("recalculateAccesses: %v", err) + } + + if err = t.getMembers(e); err != nil { + return fmt.Errorf("getMembers: %v", err) + } + for _, u := range t.Members { + if err = watchRepo(e, u.Id, repo.Id, true); err != nil { + return fmt.Errorf("watchRepo: %v", err) } } return nil @@ -505,119 +522,82 @@ func addAccessWithAuthorize(e Engine, access *Access, mode AccessType) error { // AddRepository adds new repository to team of organization. func (t *Team) AddRepository(repo *Repository) (err error) { - idStr := "$" + com.ToStr(repo.Id) + "|" - if repo.OwnerId != t.OrgId { - return errors.New("Repository not belong to organization") - } else if strings.Contains(t.RepoIds, idStr) { + if repo.OwnerId != t.OrgID { + return errors.New("Repository does not belong to organization") + } else if t.HasRepository(repo.Id) { return nil } - if err = repo.GetOwner(); err != nil { + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { return err - } else if err = t.GetMembers(); err != nil { + } + + if err = t.addRepository(sess, repo); err != nil { return err } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + return sess.Commit() +} + +func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) { + if err = removeTeamRepo(e, t.ID, repo.Id); err != nil { return err } - t.NumRepos++ - t.RepoIds += idStr - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() + t.NumRepos-- + if _, err = e.Id(t.ID).AllCols().Update(t); err != nil { return err } - // Give access to team members. - mode := AuthorizeToAccessType(t.Authorize) + // Don't need to recalculate when delete a repository from organization. + if recalculate { + if err = repo.recalculateTeamAccesses(e, t.ID); err != nil { + return err + } + } + if err = t.getMembers(e); err != nil { + return fmt.Errorf("get team members: %v", err) + } for _, u := range t.Members { - auth, err := getHighestAuthorize(sess, t.OrgId, u.Id, repo.Id, t.Id) + has, err := hasAccess(e, u, repo, ACCESS_MODE_READ) if err != nil { - sess.Rollback() return err + } else if has { + continue } - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(repo.Owner.LowerName, repo.LowerName), - } - if auth < t.Authorize { - if err = addAccessWithAuthorize(sess, access, mode); err != nil { - sess.Rollback() - return err - } - } - if err = watchRepo(sess, u.Id, repo.Id, true); err != nil { - sess.Rollback() + if err = watchRepo(e, u.Id, repo.Id, false); err != nil { return err } } - return sess.Commit() + + return nil } // RemoveRepository removes repository from team of organization. -func (t *Team) RemoveRepository(repoId int64) error { - idStr := "$" + com.ToStr(repoId) + "|" - if !strings.Contains(t.RepoIds, idStr) { +func (t *Team) RemoveRepository(repoID int64) error { + if !t.HasRepository(repoID) { return nil } - repo, err := GetRepositoryById(repoId) + repo, err := GetRepositoryById(repoID) if err != nil { return err } - if err = repo.GetOwner(); err != nil { - return err - } else if err = t.GetMembers(); err != nil { - return err - } - sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { return err } - t.NumRepos-- - t.RepoIds = strings.Replace(t.RepoIds, idStr, "", 1) - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() + if err = t.removeRepository(sess, repo, true); err != nil { return err } - // Remove access to team members. - for _, u := range t.Members { - auth, err := getHighestAuthorize(sess, t.OrgId, u.Id, repo.Id, t.Id) - if err != nil { - sess.Rollback() - return err - } - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(repo.Owner.LowerName, repo.LowerName), - } - if auth == 0 { - if _, err = sess.Delete(access); err != nil { - sess.Rollback() - return fmt.Errorf("fail to delete access: %v", err) - } else if err = watchRepo(sess, u.Id, repo.Id, false); err != nil { - sess.Rollback() - return err - } - } else if auth < t.Authorize { - if err = addAccessWithAuthorize(sess, access, AuthorizeToAccessType(auth)); err != nil { - sess.Rollback() - return err - } - } - } - return sess.Commit() } @@ -628,7 +608,7 @@ func NewTeam(t *Team) error { return ErrTeamNameIllegal } - has, err := x.Id(t.OrgId).Get(new(User)) + has, err := x.Id(t.OrgID).Get(new(User)) if err != nil { return err } else if !has { @@ -636,7 +616,7 @@ func NewTeam(t *Team) error { } t.LowerName = strings.ToLower(t.Name) - has, err = x.Where("org_id=?", t.OrgId).And("lower_name=?", t.LowerName).Get(new(Team)) + has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(new(Team)) if err != nil { return err } else if has { @@ -655,20 +635,19 @@ func NewTeam(t *Team) error { } // Update organization number of teams. - if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams + 1 WHERE id = ?", t.OrgId); err != nil { + if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { sess.Rollback() return err } return sess.Commit() } -// GetTeam returns team by given team name and organization. -func GetTeam(orgId int64, name string) (*Team, error) { +func getTeam(e Engine, orgId int64, name string) (*Team, error) { t := &Team{ - OrgId: orgId, + OrgID: orgId, LowerName: strings.ToLower(name), } - has, err := x.Get(t) + has, err := e.Get(t) if err != nil { return nil, err } else if !has { @@ -677,6 +656,11 @@ func GetTeam(orgId int64, name string) (*Team, error) { return t, nil } +// GetTeam returns team by given team name and organization. +func GetTeam(orgId int64, name string) (*Team, error) { + return getTeam(x, orgId, name) +} + func getTeamById(e Engine, teamId int64) (*Team, error) { t := new(Team) has, err := e.Id(teamId).Get(t) @@ -693,34 +677,6 @@ func GetTeamById(teamId int64) (*Team, error) { return getTeamById(x, teamId) } -func getHighestAuthorize(e Engine, orgId, uid, repoId, teamId int64) (AuthorizeType, error) { - ts, err := getUserTeams(e, orgId, uid) - if err != nil { - return 0, err - } - - var auth AuthorizeType = 0 - for _, t := range ts { - // Not current team and has given repository. - if t.Id != teamId && strings.Contains(t.RepoIds, "$"+com.ToStr(repoId)+"|") { - // Fast return. - if t.Authorize == ORG_WRITABLE { - return ORG_WRITABLE, nil - } - if t.Authorize > auth { - auth = t.Authorize - } - } - } - - return auth, nil -} - -// GetHighestAuthorize returns highest repository authorize level for given user and team. -func GetHighestAuthorize(orgId, uid, repoId, teamId int64) (AuthorizeType, error) { - return getHighestAuthorize(x, orgId, uid, repoId, teamId) -} - // UpdateTeam updates information of team. func UpdateTeam(t *Team, authChanged bool) (err error) { if !IsLegalName(t.Name) { @@ -732,60 +688,29 @@ func UpdateTeam(t *Team, authChanged bool) (err error) { } sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { return err } - // Update access for team members if needed. - if authChanged && !t.IsOwnerTeam() { - if err = t.GetRepositories(); err != nil { - return err - } else if err = t.GetMembers(); err != nil { - return err - } + t.LowerName = strings.ToLower(t.Name) + if _, err = sess.Id(t.ID).AllCols().Update(t); err != nil { + return fmt.Errorf("update: %v", err) + } - // Get organization. - org, err := GetUserById(t.OrgId) - if err != nil { - return err + // Update access for team members if needed. + if authChanged { + if err = t.getRepositories(sess); err != nil { + return fmt.Errorf("getRepositories:%v", err) } - // Update access. - mode := AuthorizeToAccessType(t.Authorize) - for _, repo := range t.Repos { - for _, u := range t.Members { - // ORG_WRITABLE is the highest authorize level for now. - // Skip checking others if current team has this level. - if t.Authorize < ORG_WRITABLE { - auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id) - if err != nil { - sess.Rollback() - return err - } - if auth >= t.Authorize { - continue // Other team has higher or same authorize level. - } - } - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(org.LowerName, repo.LowerName), - } - if err = addAccessWithAuthorize(sess, access, mode); err != nil { - sess.Rollback() - return err - } + if err = repo.recalculateTeamAccesses(sess, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %v", err) } } } - t.LowerName = strings.ToLower(t.Name) - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() - return err - } return sess.Commit() } @@ -794,64 +719,38 @@ func UpdateTeam(t *Team, authChanged bool) (err error) { func DeleteTeam(t *Team) error { if err := t.GetRepositories(); err != nil { return err - } else if err = t.GetMembers(); err != nil { - return err } // Get organization. - org, err := GetUserById(t.OrgId) + org, err := GetUserById(t.OrgID) if err != nil { return err } sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { return err } // Delete all accesses. for _, repo := range t.Repos { - for _, u := range t.Members { - auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id) - if err != nil { - sess.Rollback() - return err - } - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(org.LowerName, repo.LowerName), - } - if auth == 0 { - if _, err = sess.Delete(access); err != nil { - sess.Rollback() - return fmt.Errorf("fail to delete access: %v", err) - } - } else if auth < t.Authorize { - // Downgrade authorize level. - if err = addAccessWithAuthorize(sess, access, AuthorizeToAccessType(auth)); err != nil { - sess.Rollback() - return err - } - } + if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil { + return err } } // Delete team-user. - if _, err = sess.Where("org_id=?", org.Id).Where("team_id=?", t.Id).Delete(new(TeamUser)); err != nil { - sess.Rollback() + if _, err = sess.Where("org_id=?", org.Id).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil { return err } // Delete team. - if _, err = sess.Id(t.Id).Delete(new(Team)); err != nil { - sess.Rollback() + if _, err = sess.Id(t.ID).Delete(new(Team)); err != nil { return err } // Update organization number of teams. - if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams - 1 WHERE id = ?", t.OrgId); err != nil { - sess.Rollback() + if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { return err } @@ -867,27 +766,41 @@ func DeleteTeam(t *Team) error { // TeamUser represents an team-user relation. type TeamUser struct { - Id int64 - Uid int64 - OrgId int64 `xorm:"INDEX"` - TeamId int64 + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + Uid int64 `xorm:"UNIQUE(s)"` } -func isTeamMember(e Engine, orgId, teamId, uid int64) bool { - has, _ := e.Where("uid=?", uid).And("org_id=?", orgId).And("team_id=?", teamId).Get(new(TeamUser)) +func isTeamMember(e Engine, orgID, teamID, uid int64) bool { + has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser)) return has } // IsTeamMember returns true if given user is a member of team. -func IsTeamMember(orgId, teamId, uid int64) bool { - return isTeamMember(x, orgId, teamId, uid) +func IsTeamMember(orgID, teamID, uid int64) bool { + return isTeamMember(x, orgID, teamID, uid) +} + +func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) { + teamUsers := make([]*TeamUser, 0, 10) + if err = e.Where("team_id=?", teamID).Find(&teamUsers); err != nil { + return nil, fmt.Errorf("get team-users: %v", err) + } + members := make([]*User, 0, len(teamUsers)) + for i := range teamUsers { + member := new(User) + if _, err = e.Id(teamUsers[i].Uid).Get(member); err != nil { + return nil, fmt.Errorf("get user '%d': %v", teamUsers[i].Uid, err) + } + members = append(members, member) + } + return members, nil } // GetTeamMembers returns all members in given team of organization. -func GetTeamMembers(orgId, teamId int64) ([]*User, error) { - us := make([]*User, 0, 10) - err := x.Sql("SELECT * FROM `user` JOIN `team_user` ON `team_user`.`team_id` = ? AND `team_user`.`uid` = `user`.`id`", teamId).Find(&us) - return us, err +func GetTeamMembers(teamID int64) ([]*User, error) { + return getTeamMembers(x, teamID) } func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) { @@ -899,7 +812,7 @@ func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) { ts := make([]*Team, len(tus)) for i, tu := range tus { t := new(Team) - has, err := e.Id(tu.TeamId).Get(t) + has, err := e.Id(tu.TeamID).Get(t) if err != nil { return nil, err } else if !has { @@ -936,72 +849,40 @@ func AddTeamMember(orgId, teamId, uid int64) error { return err } - // Get organization. - org, err := GetUserById(orgId) - if err != nil { - return err - } - - // Get user. - u, err := GetUserById(uid) - if err != nil { - return err - } - sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { return err } tu := &TeamUser{ Uid: uid, - OrgId: orgId, - TeamId: teamId, + OrgID: orgId, + TeamID: teamId, } - if _, err = sess.Insert(tu); err != nil { - sess.Rollback() return err - } else if _, err = sess.Id(t.Id).Update(t); err != nil { - sess.Rollback() + } else if _, err = sess.Id(t.ID).Update(t); err != nil { return err } // Give access to team repositories. - mode := AuthorizeToAccessType(t.Authorize) for _, repo := range t.Repos { - auth, err := getHighestAuthorize(sess, t.OrgId, u.Id, repo.Id, teamId) - if err != nil { - sess.Rollback() + if err = repo.recalculateTeamAccesses(sess, 0); err != nil { return err } - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(org.LowerName, repo.LowerName), - } - if auth < t.Authorize { - if err = addAccessWithAuthorize(sess, access, mode); err != nil { - sess.Rollback() - return err - } - } } // We make sure it exists before. ou := new(OrgUser) - _, err = sess.Where("uid=?", uid).And("org_id=?", orgId).Get(ou) - if err != nil { - sess.Rollback() + if _, err = sess.Where("uid=?", uid).And("org_id=?", orgId).Get(ou); err != nil { return err } ou.NumTeams++ if t.IsOwnerTeam() { ou.IsOwner = true } - if _, err = sess.Id(ou.Id).AllCols().Update(ou); err != nil { - sess.Rollback() + if _, err = sess.Id(ou.ID).AllCols().Update(ou); err != nil { return err } @@ -1026,58 +907,32 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error { t.NumMembers-- - if err = t.GetRepositories(); err != nil { + if err = t.getRepositories(e); err != nil { return err } // Get organization. - org, err := GetUserById(orgId) - if err != nil { - return err - } - - // Get user. - u, err := GetUserById(uid) + org, err := getUserById(e, orgId) if err != nil { return err } tu := &TeamUser{ Uid: uid, - OrgId: orgId, - TeamId: teamId, + OrgID: orgId, + TeamID: teamId, } - if _, err := e.Delete(tu); err != nil { return err - } else if _, err = e.Id(t.Id).AllCols().Update(t); err != nil { + } else if _, err = e.Id(t.ID).AllCols().Update(t); err != nil { return err } // Delete access to team repositories. for _, repo := range t.Repos { - auth, err := getHighestAuthorize(e, t.OrgId, u.Id, repo.Id, teamId) - if err != nil { + if err = repo.recalculateTeamAccesses(e, 0); err != nil { return err } - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(org.LowerName, repo.LowerName), - } - // Delete access if this is the last team user belongs to. - if auth == 0 { - if _, err = e.Delete(access); err != nil { - return fmt.Errorf("fail to delete access: %v", err) - } else if err = watchRepo(e, u.Id, repo.Id, false); err != nil { - return err - } - } else if auth < t.Authorize { - // Downgrade authorize level. - if err = addAccessWithAuthorize(e, access, AuthorizeToAccessType(auth)); err != nil { - return err - } - } } // This must exist. @@ -1090,7 +945,7 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error { if t.IsOwnerTeam() { ou.IsOwner = false } - if _, err = e.Id(ou.Id).AllCols().Update(ou); err != nil { + if _, err = e.Id(ou.ID).AllCols().Update(ou); err != nil { return err } return nil @@ -1099,13 +954,77 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error { // RemoveTeamMember removes member from given team of given organization. func RemoveTeamMember(orgId, teamId, uid int64) error { sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err := sess.Begin(); err != nil { return err } if err := removeTeamMember(sess, orgId, teamId, uid); err != nil { - sess.Rollback() return err } return sess.Commit() } + +// ___________ __________ +// \__ ___/___ _____ _____\______ \ ____ ______ ____ +// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \ +// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> ) +// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/ +// \/ \/ \/ \/ \/|__| + +// TeamRepo represents an team-repository relation. +type TeamRepo struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` +} + +func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool { + has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("repo_id=?", repoID).Get(new(TeamRepo)) + return has +} + +// HasTeamRepo returns true if given repository belongs to team. +func HasTeamRepo(orgID, teamID, repoID int64) bool { + return hasTeamRepo(x, orgID, teamID, repoID) +} + +func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { + _, err := e.InsertOne(&TeamRepo{ + OrgID: orgID, + TeamID: teamID, + RepoID: repoID, + }) + return err +} + +// AddTeamRepo adds new repository relation to team. +func AddTeamRepo(orgID, teamID, repoID int64) error { + return addTeamRepo(x, orgID, teamID, repoID) +} + +func removeTeamRepo(e Engine, teamID, repoID int64) error { + _, err := e.Delete(&TeamRepo{ + TeamID: teamID, + RepoID: repoID, + }) + return err +} + +// RemoveTeamRepo deletes repository relation to team. +func RemoveTeamRepo(teamID, repoID int64) error { + return removeTeamRepo(x, teamID, repoID) +} + +func removeOrgRepo(e Engine, orgID, repoID int64) error { + _, err := e.Delete(&TeamRepo{ + OrgID: orgID, + RepoID: repoID, + }) + return err +} + +// RemoveOrgRepo removes all team-repository relations of given organization. +func RemoveOrgRepo(orgID, repoID int64) error { + return removeOrgRepo(x, orgID, repoID) +} diff --git a/models/publickey.go b/models/publickey.go index 383b85b6..6bec1139 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -130,7 +130,6 @@ func extractTypeFromBase64Key(key string) (string, error) { // Parse any key string in openssh or ssh2 format to clean openssh string (rfc4253) func ParseKeyString(content string) (string, error) { - // Transform all legal line endings to a single "\n" s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1) diff --git a/models/repo.go b/models/repo.go index cdb838a1..65cd3686 100644 --- a/models/repo.go +++ b/models/repo.go @@ -154,7 +154,6 @@ type Repository struct { IsPrivate bool IsBare bool - IsGoget bool IsMirror bool *Mirror `xorm:"-"` @@ -167,13 +166,17 @@ type Repository struct { Updated time.Time `xorm:"UPDATED"` } -func (repo *Repository) GetOwner() (err error) { +func (repo *Repository) getOwner(e Engine) (err error) { if repo.Owner == nil { - repo.Owner, err = GetUserById(repo.OwnerId) + repo.Owner, err = getUserById(e, repo.OwnerId) } return err } +func (repo *Repository) GetOwner() (err error) { + return repo.getOwner(x) +} + func (repo *Repository) GetMirror() (err error) { repo.Mirror, err = GetMirror(repo.Id) return err @@ -202,16 +205,13 @@ func (repo *Repository) RepoLink() (string, error) { return setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name, nil } -func (repo *Repository) IsOwnedBy(u *User) bool { - return repo.OwnerId == u.Id +func (repo *Repository) HasAccess(u *User) bool { + has, _ := HasAccess(u, repo, ACCESS_MODE_READ) + return has } -func (repo *Repository) HasAccess(uname string) bool { - if err := repo.GetOwner(); err != nil { - return false - } - has, _ := HasAccess(uname, path.Join(repo.Owner.Name, repo.Name), READABLE) - return has +func (repo *Repository) IsOwnedBy(u *User) bool { + return repo.OwnerId == u.Id } // DescriptionHtml does special handles to description and return HTML string. @@ -223,16 +223,12 @@ func (repo *Repository) DescriptionHtml() template.HTML { } // IsRepositoryExist returns true if the repository with given name under user has already existed. -func IsRepositoryExist(u *User, repoName string) (bool, error) { - repo := Repository{OwnerId: u.Id} - has, err := x.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo) - if err != nil { - return has, err - } else if !has { - return false, nil - } - - return com.IsDir(RepoPath(u.Name, repoName)), nil +func IsRepositoryExist(u *User, repoName string) bool { + has, _ := x.Get(&Repository{ + OwnerId: u.Id, + LowerName: strings.ToLower(repoName), + }) + return has && com.IsDir(RepoPath(u.Name, repoName)) } // CloneLink represents different types of clone URLs of repository. @@ -411,7 +407,7 @@ func createUpdateHook(repoPath string) error { } // InitRepository initializes README and .gitignore if needed. -func initRepository(f string, u *User, repo *Repository, initReadme bool, repoLang, license string) error { +func initRepository(e Engine, f string, u *User, repo *Repository, initReadme bool, repoLang, license string) error { repoPath := RepoPath(u.Name, repo.Name) // Create bare new repository. @@ -501,12 +497,12 @@ func initRepository(f string, u *User, repo *Repository, initReadme bool, repoLa if len(fileName) == 0 { // Re-fetch the repository from database before updating it (else it would // override changes that were done earlier with sql) - if repo, err = GetRepositoryById(repo.Id); err != nil { + if repo, err = getRepositoryById(e, repo.Id); err != nil { return err } repo.IsBare = true repo.DefaultBranch = "master" - return UpdateRepository(repo) + return updateRepository(e, repo) } // Apply changes and commit. @@ -514,143 +510,80 @@ func initRepository(f string, u *User, repo *Repository, initReadme bool, repoLa } // CreateRepository creates a repository for given user or organization. -func CreateRepository(u *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) { +func CreateRepository(u *User, name, desc, lang, license string, isPrivate, isMirror, initReadme bool) (_ *Repository, err error) { if !IsLegalName(name) { return nil, ErrRepoNameIllegal } - isExist, err := IsRepositoryExist(u, name) - if err != nil { - return nil, err - } else if isExist { + if IsRepositoryExist(u, name) { 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, + IsPrivate: isPrivate, } - if _, err = sess.Insert(repo); err != nil { - sess.Rollback() + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { return nil, err } - var t *Team // Owner team. - - mode := WRITABLE - if mirror { - mode = READABLE - } - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(u.LowerName, repo.LowerName), - 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 - } - if err = t.GetMembers(); err != nil { - sess.Rollback() - return nil, err - } - for _, u := range t.Members { - access.Id = 0 - 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 - } - } - - if _, err = sess.Exec( - "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { - sess.Rollback() + if _, err = sess.Insert(repo); err != nil { return nil, err - } - - // Update owner team info and count. - if u.IsOrganization() { - t.RepoIds += "$" + com.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 { + } else if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { return nil, err } + // TODO fix code for mirrors? + + // Give access to all members in owner team. if u.IsOrganization() { - t, err := u.GetOwnerTeam() + t, err := u.getOwnerTeam(sess) if err != nil { - log.Error(4, "GetOwnerTeam: %v", err) - } else { - if err = t.GetMembers(); err != nil { - log.Error(4, "GetMembers: %v", err) - } else { - for _, u := range t.Members { - if err = WatchRepo(u.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo2: %v", err) - } - } - } + return nil, fmt.Errorf("getOwnerTeam: %v", err) + } else if err = t.addRepository(sess, repo); err != nil { + return nil, fmt.Errorf("addRepository: %v", err) } } else { - if err = WatchRepo(u.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo3: %v", err) + // Organization called this in addRepository method. + if err = repo.recalculateAccesses(sess); err != nil { + return nil, fmt.Errorf("recalculateAccesses: %v", err) } } - if err = NewRepoAction(u, repo); err != nil { - log.Error(4, "NewRepoAction: %v", err) + if err = watchRepo(sess, u.Id, repo.Id, true); err != nil { + return nil, fmt.Errorf("watchRepo: %v", err) + } else if err = newRepoAction(sess, u, repo); err != nil { + return nil, fmt.Errorf("newRepoAction: %v", err) } // No need for init 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(4, "initRepository: %v", err) - return nil, fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) + if !isMirror { + repoPath := RepoPath(u.Name, repo.Name) + if err = initRepository(sess, repoPath, u, repo, initReadme, lang, license); err != nil { + if err2 := os.RemoveAll(repoPath); err2 != nil { + log.Error(4, "initRepository: %v", err) + return nil, fmt.Errorf( + "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) + } + return nil, fmt.Errorf("initRepository: %v", err) } - return nil, fmt.Errorf("initRepository: %v", err) - } - _, stderr, err := process.ExecDir(-1, - 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) + _, stderr, err := process.ExecDir(-1, + 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 + return repo, sess.Commit() } // CountRepositories returns number of repositories. @@ -686,142 +619,106 @@ func RepoPath(userName, repoName string) string { } // TransferOwnership transfers all corresponding setting from old user to new one. -func TransferOwnership(u *User, newOwner string, repo *Repository) error { - newUser, err := GetUserByName(newOwner) +func TransferOwnership(u *User, newOwnerName string, repo *Repository) error { + newOwner, err := GetUserByName(newOwnerName) if err != nil { - return fmt.Errorf("fail to get new owner(%s): %v", newOwner, err) + return fmt.Errorf("get new owner '%s': %v", newOwnerName, err) } // Check if new owner has repository with same name. - has, err := IsRepositoryExist(newUser, repo.Name) - if err != nil { - return err - } else if has { + if IsRepositoryExist(newOwner, repo.Name) { return ErrRepoAlreadyExist } sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { - return err + return fmt.Errorf("sess.Begin: %v", err) } owner := repo.Owner - oldRepoLink := path.Join(owner.LowerName, repo.LowerName) - // Delete all access first if current owner is an organization. - if owner.IsOrganization() { - if _, err = sess.Where("repo_name=?", oldRepoLink).Delete(new(Access)); err != nil { - sess.Rollback() - return fmt.Errorf("fail to delete current accesses: %v", err) - } - } else { - // Delete current owner access. - if _, err = sess.Where("repo_name=?", oldRepoLink).And("user_name=?", owner.LowerName). - Delete(new(Access)); err != nil { - sess.Rollback() - return fmt.Errorf("fail to delete access(owner): %v", err) - } - // In case new owner has access. - if _, err = sess.Where("repo_name=?", oldRepoLink).And("user_name=?", newUser.LowerName). - Delete(new(Access)); err != nil { - sess.Rollback() - return fmt.Errorf("fail to delete access(new user): %v", err) - } - } - // Change accesses to new repository path. - if _, err = sess.Where("repo_name=?", oldRepoLink). - Update(&Access{RepoName: path.Join(newUser.LowerName, repo.LowerName)}); err != nil { - sess.Rollback() - return fmt.Errorf("fail to update access(change reponame): %v", err) - } + // Note: we have to set value here to make sure recalculate accesses is based on + // new owner. + repo.OwnerId = newOwner.Id + repo.Owner = newOwner // Update repository. - repo.OwnerId = newUser.Id if _, err := sess.Id(repo.Id).Update(repo); err != nil { - sess.Rollback() - return err + return fmt.Errorf("update owner: %v", err) } - // Update user repository number. - if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", newUser.Id); err != nil { - sess.Rollback() - return err + // Remove redundant collaborators. + collaborators, err := repo.GetCollaborators() + if err != nil { + return fmt.Errorf("GetCollaborators: %v", err) } - if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", owner.Id); err != nil { - sess.Rollback() - return err + // Dummy object. + collaboration := &Collaboration{RepoID: repo.Id} + for _, c := range collaborators { + collaboration.UserID = c.Id + if c.Id == newOwner.Id || newOwner.IsOrgMember(c.Id) { + if _, err = sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %v", c.Id, err) + } + } } - mode := WRITABLE - if repo.IsMirror { - mode = READABLE - } - // New owner is organization. - if newUser.IsOrganization() { - access := &Access{ - RepoName: path.Join(newUser.LowerName, repo.LowerName), - Mode: mode, + // Remove old team-repository relations. + if owner.IsOrganization() { + if err = owner.getTeams(sess); err != nil { + return fmt.Errorf("getTeams: %v", err) } + for _, t := range owner.Teams { + if !t.hasRepository(sess, repo.Id) { + continue + } - // Give access to all members in owner team. - t, err := newUser.GetOwnerTeam() - if err != nil { - sess.Rollback() - return err - } - if err = t.GetMembers(); err != nil { - sess.Rollback() - return err - } - for _, u := range t.Members { - access.Id = 0 - access.UserName = u.LowerName - if _, err = sess.Insert(access); err != nil { - sess.Rollback() - return err + t.NumRepos-- + if _, err := sess.Id(t.ID).AllCols().Update(t); err != nil { + return fmt.Errorf("decrease team repository count '%d': %v", t.ID, err) } } - // Update owner team info and count. - t.RepoIds += "$" + com.ToStr(repo.Id) + "|" - t.NumRepos++ - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() - return err - } - } else { - access := &Access{ - RepoName: path.Join(newUser.LowerName, repo.LowerName), - UserName: newUser.LowerName, - Mode: mode, - } - if _, err = sess.Insert(access); err != nil { - sess.Rollback() - return fmt.Errorf("fail to insert access: %v", err) + if err = owner.removeOrgRepo(sess, repo.Id); err != nil { + return fmt.Errorf("removeOrgRepo: %v", err) } } - // Change repository directory name. - if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil { - sess.Rollback() - return err + if newOwner.IsOrganization() { + t, err := newOwner.GetOwnerTeam() + if err != nil { + return fmt.Errorf("GetOwnerTeam: %v", err) + } else if err = t.addRepository(sess, repo); err != nil { + return fmt.Errorf("add to owner team: %v", err) + } + } else { + // Organization called this in addRepository method. + if err = repo.recalculateAccesses(sess); err != nil { + return fmt.Errorf("recalculateAccesses: %v", err) + } } - if err = sess.Commit(); err != nil { - return err + // Update repository count. + if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.Id); err != nil { + return fmt.Errorf("increase new owner repository count: %v", err) + } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.Id); err != nil { + return fmt.Errorf("decrease old owner repository count: %v", err) } - if err = WatchRepo(newUser.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo", err) + if err = watchRepo(sess, newOwner.Id, repo.Id, true); err != nil { + return fmt.Errorf("watchRepo: %v", err) + } else if err = transferRepoAction(sess, u, owner, newOwner, repo); err != nil { + return fmt.Errorf("transferRepoAction: %v", err) } - if err = TransferRepoAction(u, newUser, repo); err != nil { - return err + // Change repository directory name. + if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename directory: %v", err) } - return nil + return sess.Commit() } // ChangeRepositoryName changes all corresponding setting from old repository name to new one. @@ -833,35 +730,11 @@ func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) return ErrRepoNameIllegal } - // Update accesses. - accesses := make([]Access, 0, 10) - if err = x.Find(&accesses, &Access{RepoName: userName + "/" + oldRepoName}); err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - for i := range accesses { - accesses[i].RepoName = userName + "/" + newRepoName - if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil { - return err - } - } - // Change repository directory name. - if err = os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)); err != nil { - sess.Rollback() - return err - } - - return sess.Commit() + return os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)) } -func UpdateRepository(repo *Repository) error { +func updateRepository(e Engine, repo *Repository) error { repo.LowerName = strings.ToLower(repo.Name) if len(repo.Description) > 255 { @@ -870,13 +743,17 @@ func UpdateRepository(repo *Repository) error { if len(repo.Website) > 255 { repo.Website = repo.Website[:255] } - _, err := x.Id(repo.Id).AllCols().Update(repo) + _, err := e.Id(repo.Id).AllCols().Update(repo) return err } +func UpdateRepository(repo *Repository) error { + return updateRepository(x, repo) +} + // DeleteRepository deletes a repository for a user or organization. -func DeleteRepository(uid, repoId int64, userName string) error { - repo := &Repository{Id: repoId, OwnerId: uid} +func DeleteRepository(uid, repoID int64, userName string) error { + repo := &Repository{Id: repoID, OwnerId: uid} has, err := x.Get(repo) if err != nil { return err @@ -896,99 +773,75 @@ func DeleteRepository(uid, repoId int64, userName string) error { } sess := x.NewSession() - defer sess.Close() + defer sessionRelease(sess) if err = sess.Begin(); err != nil { return err } - if _, err = sess.Delete(&Repository{Id: repoId}); err != nil { - sess.Rollback() - return err - } - - // Delete all access. - if _, err := sess.Delete(&Access{RepoName: strings.ToLower(path.Join(userName, repo.Name))}); err != nil { - sess.Rollback() - return err - } if org.IsOrganization() { - idStr := "$" + com.ToStr(repoId) + "|" for _, t := range org.Teams { - if !strings.Contains(t.RepoIds, idStr) { + if !t.hasRepository(sess, repoID) { continue - } - t.NumRepos-- - t.RepoIds = strings.Replace(t.RepoIds, idStr, "", 1) - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() + } else if err = t.removeRepository(sess, repo, false); err != nil { return err } } } - if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil { - sess.Rollback() + if _, err = sess.Delete(&Repository{Id: repoID}); err != nil { return err - } - if _, err = sess.Delete(&Watch{RepoId: repoId}); err != nil { - sess.Rollback() + } else if _, err = sess.Delete(&Access{RepoID: repo.Id}); err != nil { return err - } - if _, err = sess.Delete(&Mirror{RepoId: repoId}); err != nil { - sess.Rollback() + } else if _, err = sess.Delete(&Action{RepoId: repo.Id}); err != nil { return err - } - if _, err = sess.Delete(&IssueUser{RepoId: repoId}); err != nil { - sess.Rollback() + } else if _, err = sess.Delete(&Watch{RepoId: repoID}); err != nil { return err - } - if _, err = sess.Delete(&Milestone{RepoId: repoId}); err != nil { - sess.Rollback() + } else if _, err = sess.Delete(&Mirror{RepoId: repoID}); err != nil { return err - } - if _, err = sess.Delete(&Release{RepoId: repoId}); err != nil { - sess.Rollback() + } else if _, err = sess.Delete(&IssueUser{RepoId: repoID}); err != nil { + return err + } else if _, err = sess.Delete(&Milestone{RepoId: repoID}); err != nil { + return err + } else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil { + return err + } else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil { return err } // Delete comments. - if err = x.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error { - issue := bean.(*Issue) - if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil { - sess.Rollback() + issues := make([]*Issue, 0, 25) + if err = sess.Where("repo_id=?", repoID).Find(&issues); err != nil { + return err + } + for i := range issues { + if _, err = sess.Delete(&Comment{IssueId: issues[i].Id}); err != nil { return err } - return nil - }); err != nil { - sess.Rollback() - return err } - if _, err = sess.Delete(&Issue{RepoId: repoId}); err != nil { - sess.Rollback() + if _, err = sess.Delete(&Issue{RepoId: repoID}); err != nil { return err } if repo.IsFork { - if _, err = sess.Exec("UPDATE `repository` SET num_forks = num_forks - 1 WHERE id = ?", repo.ForkId); err != nil { - sess.Rollback() + if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkId); err != nil { return err } } - if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", uid); err != nil { - sess.Rollback() + if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { return err } // Remove repository files. if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil { - desc := fmt.Sprintf("Fail to delete repository files(%s/%s): %v", userName, repo.Name, err) + desc := fmt.Sprintf("delete repository files(%s/%s): %v", userName, repo.Name, err) log.Warn(desc) if err = CreateRepositoryNotice(desc); err != nil { - log.Error(4, "Fail to add notice: %v", err) + log.Error(4, "add notice: %v", err) } } + return sess.Commit() } @@ -1027,10 +880,9 @@ func GetRepositoryByName(uid int64, repoName string) (*Repository, error) { return repo, err } -// GetRepositoryById returns the repository by given id if exists. -func GetRepositoryById(id int64) (*Repository, error) { +func getRepositoryById(e Engine, id int64) (*Repository, error) { repo := &Repository{} - has, err := x.Id(id).Get(repo) + has, err := e.Id(id).Get(repo) if err != nil { return nil, err } else if !has { @@ -1039,6 +891,11 @@ func GetRepositoryById(id int64) (*Repository, error) { return repo, nil } +// GetRepositoryById returns the repository by given id if exists. +func GetRepositoryById(id int64) (*Repository, error) { + return getRepositoryById(x, id) +} + // GetRepositories returns a list of repositories of given user. func GetRepositories(uid int64, private bool) ([]*Repository, error) { repos := make([]*Repository, 0, 10) @@ -1062,73 +919,6 @@ func GetRepositoryCount(user *User) (int64, error) { return x.Count(&Repository{OwnerId: user.Id}) } -// GetCollaboratorNames returns a list of user name of repository's collaborators. -func GetCollaboratorNames(repoName string) ([]string, error) { - accesses := make([]*Access, 0, 10) - if err := x.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil { - return nil, err - } - - names := make([]string, len(accesses)) - for i := range accesses { - names[i] = accesses[i].UserName - } - return names, nil -} - -// CollaborativeRepository represents a repository with collaborative information. -type CollaborativeRepository struct { - *Repository - CanPush bool -} - -// GetCollaborativeRepos returns a list of repositories that user is collaborator. -func GetCollaborativeRepos(uname string) ([]*CollaborativeRepository, error) { - uname = strings.ToLower(uname) - accesses := make([]*Access, 0, 10) - if err := x.Find(&accesses, &Access{UserName: uname}); err != nil { - return nil, err - } - - repos := make([]*CollaborativeRepository, 0, 10) - for _, access := range accesses { - infos := strings.Split(access.RepoName, "/") - if infos[0] == uname { - continue - } - - u, err := GetUserByName(infos[0]) - if err != nil { - return nil, err - } - - repo, err := GetRepositoryByName(u.Id, infos[1]) - if err != nil { - return nil, err - } - repo.Owner = u - repos = append(repos, &CollaborativeRepository{repo, access.Mode == WRITABLE}) - } - return repos, nil -} - -// GetCollaborators returns a list of users of repository's collaborators. -func GetCollaborators(repoName string) (us []*User, err error) { - accesses := make([]*Access, 0, 10) - if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil { - return nil, err - } - - us = make([]*User, len(accesses)) - for i := range accesses { - us[i], err = GetUserByName(accesses[i].UserName) - if err != nil { - return nil, err - } - } - return us, nil -} - type SearchOption struct { Keyword string Uid int64 @@ -1276,6 +1066,94 @@ func GitGcRepos() error { }) } +// _________ .__ .__ ___. __ .__ +// \_ ___ \ ____ | | | | _____ \_ |__ ________________ _/ |_|__| ____ ____ +// / \ \/ / _ \| | | | \__ \ | __ \ / _ \_ __ \__ \\ __\ |/ _ \ / \ +// \ \___( <_> ) |_| |__/ __ \| \_\ ( <_> ) | \// __ \| | | ( <_> ) | \ +// \______ /\____/|____/____(____ /___ /\____/|__| (____ /__| |__|\____/|___| / +// \/ \/ \/ \/ \/ + +// A Collaboration is a relation between an individual and a repository +type Collaboration struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + Created time.Time `xorm:"CREATED"` +} + +// Add collaborator and accompanying access +func (repo *Repository) AddCollaborator(u *User) error { + collaboration := &Collaboration{ + RepoID: repo.Id, + UserID: u.Id, + } + + has, err := x.Get(collaboration) + if err != nil { + return err + } else if has { + return nil + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.InsertOne(collaboration); err != nil { + return err + } else if err = repo.recalculateAccesses(sess); err != nil { + return err + } + + return sess.Commit() +} + +func (repo *Repository) getCollaborators(e Engine) ([]*User, error) { + collaborations := make([]*Collaboration, 0) + if err := e.Find(&collaborations, &Collaboration{RepoID: repo.Id}); err != nil { + return nil, err + } + + users := make([]*User, len(collaborations)) + for i, c := range collaborations { + user, err := getUserById(e, c.UserID) + if err != nil { + return nil, err + } + users[i] = user + } + return users, nil +} + +// GetCollaborators returns the collaborators for a repository +func (repo *Repository) GetCollaborators() ([]*User, error) { + return repo.getCollaborators(x) +} + +// Delete collaborator and accompanying access +func (repo *Repository) DeleteCollaborator(u *User) (err error) { + collaboration := &Collaboration{ + RepoID: repo.Id, + UserID: u.Id, + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if has, err := sess.Delete(collaboration); err != nil || has == 0 { + return err + } else if err = repo.recalculateAccesses(sess); err != nil { + return err + } + + return sess.Commit() +} + // __ __ __ .__ // / \ / \_____ _/ |_ ____ | |__ // \ \/\/ /\__ \\ __\/ ___\| | \ @@ -1322,25 +1200,28 @@ func WatchRepo(uid, repoId int64, watch bool) (err error) { return watchRepo(x, uid, repoId, watch) } -// GetWatchers returns all watchers of given repository. -func GetWatchers(rid int64) ([]*Watch, error) { +func getWatchers(e Engine, rid int64) ([]*Watch, error) { watches := make([]*Watch, 0, 10) - err := x.Find(&watches, &Watch{RepoId: rid}) + err := e.Find(&watches, &Watch{RepoId: rid}) return watches, err } -// NotifyWatchers creates batch of actions for every watcher. -func NotifyWatchers(act *Action) error { +// GetWatchers returns all watchers of given repository. +func GetWatchers(rid int64) ([]*Watch, error) { + return getWatchers(x, rid) +} + +func notifyWatchers(e Engine, act *Action) error { // Add feeds for user self and all watchers. - watches, err := GetWatchers(act.RepoId) + watches, err := getWatchers(e, act.RepoId) if err != nil { - return errors.New("repo.NotifyWatchers(get watches): " + err.Error()) + return fmt.Errorf("get watchers: %v", err) } // Add feed for actioner. act.UserId = act.ActUserId - if _, err = x.InsertOne(act); err != nil { - return errors.New("repo.NotifyWatchers(create action): " + err.Error()) + if _, err = e.InsertOne(act); err != nil { + return fmt.Errorf("insert new actioner: %v", err) } for i := range watches { @@ -1350,13 +1231,18 @@ func NotifyWatchers(act *Action) error { act.Id = 0 act.UserId = watches[i].UserId - if _, err = x.InsertOne(act); err != nil { - return errors.New("repo.NotifyWatchers(create action): " + err.Error()) + if _, err = e.InsertOne(act); err != nil { + return fmt.Errorf("insert new action: %v", err) } } return nil } +// NotifyWatchers creates batch of actions for every watcher. +func NotifyWatchers(act *Action) error { + return notifyWatchers(x, act) +} + // _________ __ // / _____// |______ _______ // \_____ \\ __\__ \\_ __ \ @@ -1409,11 +1295,8 @@ func IsStaring(uid, repoId int64) bool { // \___ / \____/|__| |__|_ \ // \/ \/ -func ForkRepository(u *User, oldRepo *Repository, name, desc string) (*Repository, error) { - isExist, err := IsRepositoryExist(u, name) - if err != nil { - return nil, err - } else if isExist { +func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { + if IsRepositoryExist(u, name) { return nil, ErrRepoAlreadyExist } @@ -1425,12 +1308,6 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (*Repositor } } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - repo := &Repository{ OwnerId: u.Id, Owner: u, @@ -1442,101 +1319,47 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (*Repositor ForkId: oldRepo.Id, } - if _, err = sess.Insert(repo); err != nil { - sess.Rollback() + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { return nil, err } - var t *Team // Owner team. - - mode := WRITABLE - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(u.LowerName, repo.LowerName), - 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 - } - if err = t.GetMembers(); err != nil { - sess.Rollback() - return nil, err - } - for _, u := range t.Members { - access.Id = 0 - 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 - } - } - - if _, err = sess.Exec( - "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { - sess.Rollback() + if _, err = sess.Insert(repo); err != nil { return nil, err } - // Update owner team info and count. - if u.IsOrganization() { - t.RepoIds += "$" + com.ToStr(repo.Id) + "|" - t.NumRepos++ - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() - return nil, err - } + if err = repo.recalculateAccesses(sess); err != nil { + return nil, err + } else if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { + return nil, err } if u.IsOrganization() { - t, err := u.GetOwnerTeam() + // Update owner team info and count. + t, err := u.getOwnerTeam(sess) if err != nil { - log.Error(4, "GetOwnerTeam: %v", err) - } else { - if err = t.GetMembers(); err != nil { - log.Error(4, "GetMembers: %v", err) - } else { - for _, u := range t.Members { - if err = watchRepo(sess, u.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo2: %v", err) - } - } - } + return nil, fmt.Errorf("getOwnerTeam: %v", err) + } else if err = t.addRepository(sess, repo); err != nil { + return nil, fmt.Errorf("addRepository: %v", err) } } else { if err = watchRepo(sess, u.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo3: %v", err) + return nil, fmt.Errorf("watchRepo: %v", err) } } - if err = NewRepoAction(u, repo); err != nil { - log.Error(4, "NewRepoAction: %v", err) + if err = newRepoAction(sess, u, repo); err != nil { + return nil, fmt.Errorf("newRepoAction: %v", err) } - if _, err = sess.Exec( - "UPDATE `repository` SET num_forks = num_forks + 1 WHERE id = ?", oldRepo.Id); err != nil { - sess.Rollback() + if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.Id); err != nil { return nil, err } oldRepoPath, err := oldRepo.RepoPath() if err != nil { - sess.Rollback() - return nil, fmt.Errorf("fail to get repo path(%s): %v", oldRepo.Name, err) - } - - if err = sess.Commit(); err != nil { - return nil, err + return nil, fmt.Errorf("get old repository path: %v", err) } repoPath := RepoPath(u.Name, repo.Name) @@ -1544,15 +1367,15 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (*Repositor fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name), "git", "clone", "--bare", oldRepoPath, repoPath) if err != nil { - return nil, errors.New("ForkRepository(git clone): " + stderr) + return nil, fmt.Errorf("git clone: %v", stderr) } _, stderr, err = process.ExecDir(-1, repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath), "git", "update-server-info") if err != nil { - return nil, errors.New("ForkRepository(git update-server-info): " + stderr) + return nil, fmt.Errorf("git update-server-info: %v", err) } - return repo, nil + return repo, sess.Commit() } diff --git a/models/user.go b/models/user.go index 2da0881c..ea5041bd 100644 --- a/models/user.go +++ b/models/user.go @@ -231,7 +231,7 @@ func (u *User) GetOrganizations() error { u.Orgs = make([]*User, len(ous)) for i, ou := range ous { - u.Orgs[i], err = GetUserById(ou.OrgId) + u.Orgs[i], err = GetUserById(ou.OrgID) if err != nil { return err } @@ -249,11 +249,13 @@ func (u *User) GetFullNameFallback() string { // IsUserExist checks if given user name exist, // the user name should be noncased unique. -func IsUserExist(name string) (bool, error) { +// If uid is presented, then check will rule out that one, +// it is used when update a user name in settings page. +func IsUserExist(uid int64, name string) (bool, error) { if len(name) == 0 { return false, nil } - return x.Get(&User{LowerName: strings.ToLower(name)}) + return x.Where("id!=?", uid).Get(&User{LowerName: strings.ToLower(name)}) } // IsEmailUsed returns true if the e-mail has been used. @@ -278,7 +280,7 @@ func CreateUser(u *User) error { return ErrUserNameIllegal } - isExist, err := IsUserExist(u.Name) + isExist, err := IsUserExist(0, u.Name) if err != nil { return err } else if isExist { @@ -396,64 +398,12 @@ func ChangeUserName(u *User, newUserName string) (err error) { return ErrUserNameIllegal } - newUserName = strings.ToLower(newUserName) - - // Update accesses of user. - accesses := make([]Access, 0, 10) - if err = x.Find(&accesses, &Access{UserName: u.LowerName}); err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - for i := range accesses { - accesses[i].UserName = newUserName - if strings.HasPrefix(accesses[i].RepoName, u.LowerName+"/") { - accesses[i].RepoName = strings.Replace(accesses[i].RepoName, u.LowerName, newUserName, 1) - } - if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil { - return err - } - } - - repos, err := GetRepositories(u.Id, true) - if err != nil { - return err - } - for i := range repos { - accesses = make([]Access, 0, 10) - // Update accesses of user repository. - if err = x.Find(&accesses, &Access{RepoName: u.LowerName + "/" + repos[i].LowerName}); err != nil { - return err - } - - for j := range accesses { - // if the access is not the user's access (already updated above) - if accesses[j].UserName != u.LowerName { - accesses[j].RepoName = newUserName + "/" + repos[i].LowerName - if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil { - return err - } - } - } - } - - // Change user directory name. - if err = os.Rename(UserPath(u.LowerName), UserPath(newUserName)); err != nil { - sess.Rollback() - return err - } - - return sess.Commit() + return os.Rename(UserPath(u.LowerName), UserPath(newUserName)) } // UpdateUser updates user's information. func UpdateUser(u *User) error { - has, err := x.Where("id!=?", u.Id).And("type=?", INDIVIDUAL).And("email=?", u.Email).Get(new(User)) + has, err := x.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User)) if err != nil { return err } else if has { @@ -521,7 +471,7 @@ func DeleteUser(u *User) error { return err } // Delete all accesses. - if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil { + if _, err = x.Delete(&Access{UserID: u.Id}); err != nil { return err } // Delete all alternative email addresses @@ -564,8 +514,7 @@ func UserPath(userName string) string { func GetUserByKeyId(keyId int64) (*User, error) { user := new(User) - rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?" - has, err := x.Sql(rawSql, keyId).Get(user) + has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user) if err != nil { return nil, err } else if !has { @@ -574,10 +523,9 @@ func GetUserByKeyId(keyId int64) (*User, error) { return user, nil } -// GetUserById returns the user object by given ID if exists. -func GetUserById(id int64) (*User, error) { +func getUserById(e Engine, id int64) (*User, error) { u := new(User) - has, err := x.Id(id).Get(u) + has, err := e.Id(id).Get(u) if err != nil { return nil, err } else if !has { @@ -586,6 +534,11 @@ func GetUserById(id int64) (*User, error) { return u, nil } +// GetUserById returns the user object by given ID if exists. +func GetUserById(id int64) (*User, error) { + return getUserById(x, id) +} + // GetUserByName returns user by given name. func GetUserByName(name string) (*User, error) { if len(name) == 0 { @@ -627,7 +580,7 @@ func GetUserIdsByNames(names []string) []int64 { return ids } -// Get all email addresses +// GetEmailAddresses returns all e-mail addresses belongs to given user. func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { emails := make([]*EmailAddress, 0, 5) err := x.Where("uid=?", uid).Find(&emails) @@ -641,7 +594,6 @@ func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { } isPrimaryFound := false - for _, email := range emails { if email.Email == u.Email { isPrimaryFound = true @@ -654,7 +606,11 @@ func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { // We alway want the primary email address displayed, even if it's not in // the emailaddress table (yet) if !isPrimaryFound { - emails = append(emails, &EmailAddress{Email: u.Email, IsActivated: true, IsPrimary: true}) + emails = append(emails, &EmailAddress{ + Email: u.Email, + IsActivated: true, + IsPrimary: true, + }) } return emails, nil } @@ -904,7 +860,7 @@ func UpdateMentions(userNames []string, issueId int64) error { } for _, orgUser := range orgUsers { - tempIds = append(tempIds, orgUser.Id) + tempIds = append(tempIds, orgUser.ID) } ids = append(ids, tempIds...) diff --git a/models/webhook.go b/models/webhook.go index 34349bb5..96af0b69 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -315,7 +315,7 @@ func DeliverHooks() { req := httplib.Post(t.Url).SetTimeout(timeout, timeout). Header("X-Gogs-Delivery", t.Uuid). Header("X-Gogs-Event", string(t.EventType)). - SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.AllowInsecureCertification}) + SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify}) switch t.ContentType { case JSON: diff --git a/modules/auth/auth.go b/modules/auth/auth.go index ad7ce5b9..5b24591a 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -108,17 +108,16 @@ func SignedInUser(req *http.Request, sess session.Store) (*models.User, bool) { auths := strings.Fields(baHead) if len(auths) == 2 && auths[0] == "Basic" { uname, passwd, _ := base.BasicAuthDecode(auths[1]) - u, err := models.GetUserByName(uname) + + u, err := models.UserSignIn(uname, passwd) if err != nil { if err != models.ErrUserNotExist { - log.Error(4, "GetUserByName: %v", err) + log.Error(4, "UserSignIn: %v", err) } return nil, false } - if u.ValidtePassword(passwd) { - return u, true - } + return u, true } } return nil, false diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index e9789634..c7b93896 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -18,7 +18,10 @@ type AuthenticationForm struct { Port int `form:"port"` UseSSL bool `form:"usessl"` BaseDN string `form:"base_dn"` - Attributes string `form:"attributes"` + AttributeUsername string `form:"attribute_username"` + AttributeName string `form:"attribute_name"` + AttributeSurname string `form:"attribute_surname"` + AttributeMail string `form:"attribute_mail"` Filter string `form:"filter"` MsAdSA string `form:"ms_ad_sa"` IsActived bool `form:"is_actived"` diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index 44c130a1..c78e241d 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -15,15 +15,18 @@ import ( // Basic LDAP authentication service type Ldapsource struct { - Name string // canonical name (ie. corporate.ad) - Host string // LDAP host - Port int // port number - UseSSL bool // Use SSL - BaseDN string // Base DN - Attributes string // Attribute to search - Filter string // Query filter to validate entry - MsAdSAFormat string // in the case of MS AD Simple Authen, the format to use (see: http://msdn.microsoft.com/en-us/library/cc223499.aspx) - Enabled bool // if this source is disabled + Name string // canonical name (ie. corporate.ad) + Host string // LDAP host + Port int // port number + UseSSL bool // Use SSL + BaseDN string // Base DN + AttributeUsername string // Username attribute + AttributeName string // First name attribute + AttributeSurname string // Surname attribute + AttributeMail string // E-mail attribute + Filter string // Query filter to validate entry + MsAdSAFormat string // in the case of MS AD Simple Authen, the format to use (see: http://msdn.microsoft.com/en-us/library/cc223499.aspx) + Enabled bool // if this source is disabled } //Global LDAP directory pool @@ -32,18 +35,18 @@ var ( ) // Add a new source (LDAP directory) to the global pool -func AddSource(name string, host string, port int, usessl bool, basedn string, attributes string, filter string, msadsaformat string) { - ldaphost := Ldapsource{name, host, port, usessl, basedn, attributes, filter, msadsaformat, true} +func AddSource(name string, host string, port int, usessl bool, basedn string, attribcn string, attribname string, attribsn string, attribmail string, filter string, msadsaformat string) { + ldaphost := Ldapsource{name, host, port, usessl, basedn, attribcn, attribname, attribsn, attribmail, filter, msadsaformat, true} Authensource = append(Authensource, ldaphost) } //LoginUser : try to login an user to LDAP sources, return requested (attribute,true) if ok, ("",false) other wise //First match wins //Returns first attribute if exists -func LoginUser(name, passwd string) (a string, r bool) { +func LoginUser(name, passwd string) (cn, fn, sn, mail string, r bool) { r = false for _, ls := range Authensource { - a, r = ls.SearchEntry(name, passwd) + cn, fn, sn, mail, r = ls.SearchEntry(name, passwd) if r { return } @@ -52,12 +55,12 @@ func LoginUser(name, passwd string) (a string, r bool) { } // searchEntry : search an LDAP source if an entry (name, passwd) is valide and in the specific filter -func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) { +func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, string, bool) { l, err := ldapDial(ls) if err != nil { log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err) ls.Enabled = false - return "", false + return "", "", "", "", false } defer l.Close() @@ -65,26 +68,29 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) { err = l.Bind(nx, passwd) if err != nil { log.Debug("LDAP Authan failed for %s, reason: %s", nx, err.Error()) - return "", false + return "", "", "", "", false } search := ldap.NewSearchRequest( ls.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf(ls.Filter, name), - []string{ls.Attributes}, + []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, nil) sr, err := l.Search(search) if err != nil { log.Debug("LDAP Authen OK but not in filter %s", name) - return "", false + return "", "", "", "", false } log.Debug("LDAP Authen OK: %s", name) if len(sr.Entries) > 0 { - r := sr.Entries[0].GetAttributeValue(ls.Attributes) - return r, true + cn := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) + name := sr.Entries[0].GetAttributeValue(ls.AttributeName) + sn := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) + mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail) + return cn, name, sn, mail, true } - return "", true + return "", "", "", "", true } func ldapDial(ls Ldapsource) (*ldap.Conn, error) { diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 36e62f04..2902a92f 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -31,7 +31,7 @@ func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bin } type MigrateRepoForm struct { - HttpsUrl string `form:"url" binding:"Required;Url"` + CloneAddr string `binding:"Required"` AuthUserName string `form:"auth_username"` AuthPasswd string `form:"auth_password"` Uid int64 `form:"uid" binding:"Required"` @@ -52,7 +52,6 @@ type RepoSettingForm struct { Branch string `form:"branch"` Interval int `form:"interval"` Private bool `form:"private"` - GoGet bool `form:"goget"` } func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 3c0ff651..b616a460 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -99,7 +99,7 @@ func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) b } type AddEmailForm struct { - Email string `form:"email" binding:"Required;Email;MaxSize(50)"` + Email string `binding:"Required;Email;MaxSize(50)"` } func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { diff --git a/modules/base/template.go b/modules/base/template.go index f3fa1385..196b9351 100644 --- a/modules/base/template.go +++ b/modules/base/template.go @@ -41,6 +41,10 @@ func List(l *list.List) chan interface{} { return c } +func Sha1(str string) string { + return EncodeSha1(str) +} + func ShortSha(sha1 string) string { if len(sha1) == 40 { return sha1[:10] @@ -126,8 +130,13 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ return a + b }, "ActionIcon": ActionIcon, - "DateFormat": DateFormat, - "List": List, + "DateFmtLong": func(t time.Time) string { + return t.Format(time.RFC1123Z) + }, + "DateFmtShort": func(t time.Time) string { + return t.Format("Jan 02, 2006") + }, + "List": List, "Mail2Domain": func(mail string) string { if !strings.Contains(mail, "@") { return "try.gogs.io" @@ -155,6 +164,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ }, "DiffTypeToStr": DiffTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr, + "Sha1": Sha1, "ShortSha": ShortSha, "Md5": EncodeMd5, "ActionContent2Commits": ActionContent2Commits, diff --git a/modules/base/tool.go b/modules/base/tool.go index 5043364c..55e6dffd 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -126,7 +126,7 @@ func VerifyTimeLimitCode(data string, minutes int, code string) bool { retCode := CreateTimeLimitCode(data, minutes, start) if retCode == code && minutes > 0 { // check time is expired or not - before, _ := DateParse(start, "YmdHi") + before, _ := time.ParseInLocation("200601021504", start, time.Local) now := time.Now() if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() { return true @@ -141,7 +141,7 @@ const TimeLimitCodeLength = 12 + 6 + 40 // create a time limit code // code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string { - format := "YmdHi" + format := "200601021504" var start, end time.Time var startStr, endStr string @@ -149,16 +149,16 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string if startInf == nil { // Use now time create code start = time.Now() - startStr = DateFormat(start, format) + startStr = start.Format(format) } else { // use start string create code startStr = startInf.(string) - start, _ = DateParse(startStr, format) - startStr = DateFormat(start, format) + start, _ = time.ParseInLocation(format, startStr, time.Local) + startStr = start.Format(format) } end = start.Add(time.Minute * time.Duration(minutes)) - endStr = DateFormat(end, format) + endStr = end.Format(format) // create sha1 encode string sh := sha1.New() @@ -420,58 +420,3 @@ func Subtract(left interface{}, right interface{}) interface{} { return fleft + float64(rleft) - (fright + float64(rright)) } } - -// DateFormat pattern rules. -var datePatterns = []string{ - // year - "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 - "y", "06", //A two digit representation of a year Examples: 99 or 03 - - // month - "m", "01", // Numeric representation of a month, with leading zeros 01 through 12 - "n", "1", // Numeric representation of a month, without leading zeros 1 through 12 - "M", "Jan", // A short textual representation of a month, three letters Jan through Dec - "F", "January", // A full textual representation of a month, such as January or March January through December - - // day - "d", "02", // Day of the month, 2 digits with leading zeros 01 to 31 - "j", "2", // Day of the month without leading zeros 1 to 31 - - // week - "D", "Mon", // A textual representation of a day, three letters Mon through Sun - "l", "Monday", // A full textual representation of the day of the week Sunday through Saturday - - // time - "g", "3", // 12-hour format of an hour without leading zeros 1 through 12 - "G", "15", // 24-hour format of an hour without leading zeros 0 through 23 - "h", "03", // 12-hour format of an hour with leading zeros 01 through 12 - "H", "15", // 24-hour format of an hour with leading zeros 00 through 23 - - "a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm - "A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM - - "i", "04", // Minutes with leading zeros 00 to 59 - "s", "05", // Seconds, with leading zeros 00 through 59 - - // time zone - "T", "MST", - "P", "-07:00", - "O", "-0700", - - // RFC 2822 - "r", time.RFC1123Z, -} - -// Parse Date use PHP time format. -func DateParse(dateString, format string) (time.Time, error) { - replacer := strings.NewReplacer(datePatterns...) - format = replacer.Replace(format) - return time.ParseInLocation(format, dateString, time.Local) -} - -// Date takes a PHP like date func to Go's time format. -func DateFormat(t time.Time, format string) string { - replacer := strings.NewReplacer(datePatterns...) - format = replacer.Replace(format) - return t.Format(format) -} diff --git a/modules/git/signature.go b/modules/git/signature.go index 20f647d2..ad9c1b39 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -17,24 +17,35 @@ type Signature struct { When time.Time } -// Helper to get a signature from the commit line, which looks like this: +// Helper to get a signature from the commit line, which looks like these: // author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200 +// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200 // but without the "author " at the beginning (this method should) // be used for author and committer. // -// FIXME: include timezone! -func newSignatureFromCommitline(line []byte) (*Signature, error) { +// FIXME: include timezone for timestamp! +func newSignatureFromCommitline(line []byte) (_ *Signature, err error) { sig := new(Signature) emailstart := bytes.IndexByte(line, '<') sig.Name = string(line[:emailstart-1]) emailstop := bytes.IndexByte(line, '>') sig.Email = string(line[emailstart+1 : emailstop]) - timestop := bytes.IndexByte(line[emailstop+2:], ' ') - timestring := string(line[emailstop+2 : emailstop+2+timestop]) - seconds, err := strconv.ParseInt(timestring, 10, 64) - if err != nil { - return nil, err + + // Check date format. + firstChar := line[emailstop+2] + if firstChar >= 48 && firstChar <= 57 { + timestop := bytes.IndexByte(line[emailstop+2:], ' ') + timestring := string(line[emailstop+2 : emailstop+2+timestop]) + seconds, err := strconv.ParseInt(timestring, 10, 64) + if err != nil { + return nil, err + } + sig.When = time.Unix(seconds, 0) + } else { + sig.When, err = time.Parse(time.RFC1123Z, string(line[emailstop+2:])) + if err != nil { + return nil, err + } } - sig.When = time.Unix(seconds, 0) return sig, nil } diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index f658427c..74a3fca5 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -10,6 +10,7 @@ import ( "net" "net/mail" "net/smtp" + "os" "strings" "github.com/gogits/gogs/modules/log" @@ -103,6 +104,15 @@ func sendMail(settings *setting.Mailer, recipients []string, msgContent []byte) return err } + hostname, err := os.Hostname() + if err != nil { + return err + } + + if err = client.Hello(hostname); err != nil { + return err + } + // If not using SMTPS, alway use STARTTLS if available hasStartTLS, _ := client.Extension("STARTTLS") if !isSecureConn && hasStartTLS { diff --git a/modules/middleware/context.go b/modules/middleware/context.go index 28be3a30..dc3b5cad 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -38,29 +38,7 @@ type Context struct { IsSigned bool IsBasicAuth bool - Repo struct { - IsOwner bool - IsTrueOwner bool - IsWatching bool - IsBranch bool - IsTag bool - IsCommit bool - IsAdmin bool // Current user is admin level. - HasAccess bool - Repository *models.Repository - Owner *models.User - Commit *git.Commit - Tag *git.Tag - GitRepo *git.Repository - BranchName string - TagName string - TreeName string - CommitId string - RepoLink string - CloneLink models.CloneLink - CommitsCount int - Mirror *models.Mirror - } + Repo RepoContext Org struct { IsOwner bool @@ -73,6 +51,37 @@ type Context struct { } } +type RepoContext struct { + AccessMode models.AccessMode + IsWatching bool + IsBranch bool + IsTag bool + IsCommit bool + Repository *models.Repository + Owner *models.User + Commit *git.Commit + Tag *git.Tag + GitRepo *git.Repository + BranchName string + TagName string + TreeName string + CommitId string + RepoLink string + CloneLink models.CloneLink + CommitsCount int + Mirror *models.Mirror +} + +// Return if the current user has write access for this repository +func (r RepoContext) IsOwner() bool { + return r.AccessMode >= models.ACCESS_MODE_WRITE +} + +// Return if the current user has read access for this repository +func (r RepoContext) HasAccess() bool { + return r.AccessMode >= models.ACCESS_MODE_READ +} + // HasError returns true if error occurs in form validation. func (ctx *Context) HasApiError() bool { hasErr, ok := ctx.Data["HasError"] @@ -130,6 +139,18 @@ func (ctx *Context) Handle(status int, title string, err error) { ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status))) } +func (ctx *Context) HandleAPI(status int, obj interface{}) { + var message string + if err, ok := obj.(error); ok { + message = err.Error() + } else { + message = obj.(string) + } + ctx.JSON(status, map[string]string{ + "message": message, + }) +} + func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) { modtime := time.Now() for _, p := range params { diff --git a/modules/middleware/org.go b/modules/middleware/org.go index e6872586..0e544fe4 100644 --- a/modules/middleware/org.go +++ b/modules/middleware/org.go @@ -87,7 +87,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { return } ctx.Data["Team"] = ctx.Org.Team - ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize == models.ORG_ADMIN + ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize >= models.ACCESS_MODE_ADMIN } ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam if requireAdminTeam && !ctx.Org.IsAdminTeam { diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index 1ab158dd..3350c03d 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -5,7 +5,6 @@ package middleware import ( - "errors" "fmt" "net/url" "strings" @@ -29,17 +28,10 @@ func ApiRepoAssignment() macaron.Handler { err error ) - // Collaborators who have write access can be seen as owners. - if ctx.IsSigned { - ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE) - if err != nil { - ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL}) - return - } - ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName) - } - - if !ctx.Repo.IsTrueOwner { + // Check if the user is the same as the repository owner. + if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { + u = ctx.User + } else { u, err = models.GetUserByName(userName) if err != nil { if err == models.ErrUserNotExist { @@ -49,66 +41,36 @@ func ApiRepoAssignment() macaron.Handler { } return } - } else { - u = ctx.User } ctx.Repo.Owner = u - // Organization owner team members are true owners as well. - if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) { - ctx.Repo.IsTrueOwner = true - } - // Get repository. repo, err := models.GetRepositoryByName(u.Id, repoName) if err != nil { if err == models.ErrRepoNotExist { ctx.Error(404) - return + } else { + ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL}) } - ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL}) return } else if err = repo.GetOwner(); err != nil { ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL}) return } - // Check if the mirror repository owner(mirror repository doesn't have access). - if ctx.IsSigned && !ctx.Repo.IsOwner { - if repo.OwnerId == ctx.User.Id { - ctx.Repo.IsOwner = true - } - // Check if current user has admin permission to repository. - if u.IsOrganization() { - auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0) - if err != nil { - ctx.JSON(500, &base.ApiJsonErr{"GetHighestAuthorize: " + err.Error(), base.DOC_URL}) - return - } - if auth == models.ORG_ADMIN { - ctx.Repo.IsOwner = true - ctx.Repo.IsAdmin = true - } - } + mode, err := models.AccessLevel(ctx.User, repo) + if err != nil { + ctx.JSON(500, &base.ApiJsonErr{"AccessLevel: " + err.Error(), base.DOC_URL}) + return } - // Check access. - if repo.IsPrivate && !ctx.Repo.IsOwner { - if ctx.User == nil { - ctx.Error(404) - return - } + ctx.Repo.AccessMode = mode - hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE) - if err != nil { - ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL}) - return - } else if !hasAccess { - ctx.Error(404) - return - } + // Check access. + if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE { + ctx.Error(404) + return } - ctx.Repo.HasAccess = true ctx.Repo.Repository = repo } @@ -242,101 +204,49 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { refName = ctx.Params(":path") } - // Collaborators who have write access can be seen as owners. - if ctx.IsSigned { - ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE) - if err != nil { - ctx.Handle(500, "HasAccess", err) - return - } - ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName) - } - - if !ctx.Repo.IsTrueOwner { + // Check if the user is the same as the repository owner + if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { + u = ctx.User + } else { u, err = models.GetUserByName(userName) if err != nil { if err == models.ErrUserNotExist { ctx.Handle(404, "GetUserByName", err) - } else if redirect { - log.Error(4, "GetUserByName", err) - ctx.Redirect(setting.AppSubUrl + "/") } else { ctx.Handle(500, "GetUserByName", err) } return } - } else { - u = ctx.User - } - - if u == nil { - if redirect { - ctx.Redirect(setting.AppSubUrl + "/") - return - } - ctx.Handle(404, "RepoAssignment", errors.New("invliad user account for single repository")) - return } ctx.Repo.Owner = u - // Organization owner team members are true owners as well. - if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) { - ctx.Repo.IsTrueOwner = true - } - // Get repository. repo, err := models.GetRepositoryByName(u.Id, repoName) if err != nil { if err == models.ErrRepoNotExist { ctx.Handle(404, "GetRepositoryByName", err) - return - } else if redirect { - ctx.Redirect(setting.AppSubUrl + "/") - return + } else { + ctx.Handle(500, "GetRepositoryByName", err) } - ctx.Handle(500, "GetRepositoryByName", err) return } else if err = repo.GetOwner(); err != nil { ctx.Handle(500, "GetOwner", err) return } - // Check if the mirror repository owner(mirror repository doesn't have access). - if ctx.IsSigned && !ctx.Repo.IsOwner { - if repo.OwnerId == ctx.User.Id { - ctx.Repo.IsOwner = true - } - // Check if current user has admin permission to repository. - if u.IsOrganization() { - auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0) - if err != nil { - ctx.Handle(500, "GetHighestAuthorize", err) - return - } - if auth == models.ORG_ADMIN { - ctx.Repo.IsOwner = true - ctx.Repo.IsAdmin = true - } - } + mode, err := models.AccessLevel(ctx.User, repo) + if err != nil { + ctx.Handle(500, "AccessLevel", err) + return } + ctx.Repo.AccessMode = mode // Check access. - if repo.IsPrivate && !ctx.Repo.IsOwner { - if ctx.User == nil { - ctx.Handle(404, "HasAccess", nil) - return - } - - hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE) - if err != nil { - ctx.Handle(500, "HasAccess", err) - return - } else if !hasAccess { - ctx.Handle(404, "HasAccess", nil) - return - } + if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE { + ctx.Handle(404, "no access right", err) + return } - ctx.Repo.HasAccess = true + ctx.Data["HasAccess"] = true if repo.IsMirror { @@ -383,8 +293,8 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { ctx.Data["Title"] = u.Name + "/" + repo.Name ctx.Data["Repository"] = repo ctx.Data["Owner"] = ctx.Repo.Repository.Owner - ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner - ctx.Data["IsRepositoryTrueOwner"] = ctx.Repo.IsTrueOwner + ctx.Data["IsRepositoryOwner"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_WRITE + ctx.Data["IsRepositoryAdmin"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_ADMIN ctx.Data["DisableSSH"] = setting.DisableSSH ctx.Repo.CloneLink, err = repo.CloneLink() @@ -394,8 +304,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { } ctx.Data["CloneLink"] = ctx.Repo.CloneLink - if ctx.Repo.Repository.IsGoget { - ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", setting.AppUrl, u.LowerName, repo.LowerName) + if ctx.Query("go-get") == "1" { ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", setting.Domain, u.LowerName, repo.LowerName) } @@ -439,9 +348,9 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { } } -func RequireTrueOwner() macaron.Handler { +func RequireAdmin() macaron.Handler { return func(ctx *Context) { - if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin { + if ctx.Repo.AccessMode < models.ACCESS_MODE_ADMIN { if !ctx.IsSigned { ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl) ctx.Redirect(setting.AppSubUrl + "/user/login") diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 32284b42..fd07c17f 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -67,11 +67,16 @@ var ( CookieRememberName string ReverseProxyAuthUser string + // Database settings. + UseSQLite3 bool + UseMySQL bool + UsePostgreSQL bool + // Webhook settings. Webhook struct { - TaskInterval int - DeliverTimeout int - AllowInsecureCertification bool + TaskInterval int + DeliverTimeout int + SkipTLSVerify bool } // Repository settings. @@ -240,7 +245,10 @@ func NewConfigContext() { ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") sec = Cfg.Section("attachment") - AttachmentPath = path.Join(workDir, sec.Key("PATH").MustString("data/attachments")) + AttachmentPath = sec.Key("PATH").MustString("data/attachments") + if !filepath.IsAbs(AttachmentPath) { + AttachmentPath = path.Join(workDir, AttachmentPath) + } AttachmentAllowedTypes = sec.Key("ALLOWED_TYPES").MustString("image/jpeg|image/png") AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(32) AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(10) @@ -264,10 +272,6 @@ func NewConfigContext() { "StampNano": time.StampNano, }[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")] - if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil { - log.Fatal(4, "Could not create directory %s: %s", AttachmentPath, err) - } - RunUser = Cfg.Section("").Key("RUN_USER").String() curUser := os.Getenv("USER") if len(curUser) == 0 { @@ -290,15 +294,14 @@ func NewConfigContext() { } else { RepoRootPath = filepath.Clean(RepoRootPath) } - if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil { - log.Fatal(4, "Fail to create repository root path(%s): %v", RepoRootPath, err) - } ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash") sec = Cfg.Section("picture") PictureService = sec.Key("SERVICE").In("server", []string{"server"}) - AvatarUploadPath = path.Join(workDir, sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars")) - os.MkdirAll(AvatarUploadPath, os.ModePerm) + AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars") + if !filepath.IsAbs(AvatarUploadPath) { + AvatarUploadPath = path.Join(workDir, AvatarUploadPath) + } switch sec.Key("GRAVATAR_SOURCE").MustString("gravatar") { case "duoshuo": GravatarSource = "http://gravatar.duoshuo.com/avatar/" @@ -363,9 +366,11 @@ func newLogService() { log.Fatal(4, "Unknown log mode: %s", mode) } + validLevels := []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"} // Log level. - levelName := Cfg.Section("log."+mode).Key("LEVEL").In("Trace", - []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"}) + levelName := Cfg.Section("log."+mode).Key("LEVEL").In( + Cfg.Section("log").Key("LEVEL").In("Trace", validLevels), + validLevels) level, ok := logLevels[levelName] if !ok { log.Fatal(4, "Unknown log level: %s", levelName) @@ -519,7 +524,7 @@ func newWebhookService() { sec := Cfg.Section("webhook") Webhook.TaskInterval = sec.Key("TASK_INTERVAL").MustInt(1) Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) - Webhook.AllowInsecureCertification = sec.Key("ALLOW_INSECURE_CERTIFICATION").MustBool() + Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() } func NewServices() { diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index 71208f4b..43931e94 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -1086,6 +1086,7 @@ The register and sign-in page style border-right: none; width: 190px; border-left: none; + cursor: default; } #repo-clone-help { clear: both; diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index a6b9753e..c5fd719c 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -209,27 +209,28 @@ var Gogs = {}; $list.parents('tr').removeClass('end-selected-line'); $list.parents('tr').find('td').removeClass('selected-line'); if ($from) { - var expr = new RegExp(/diff-(\d+)L(\d+)/); + var expr = new RegExp(/diff-(\w+)([LR]\d+)/); var selectMatches = $select.attr('rel').match(expr) var fromMatches = $from.attr('rel').match(expr) - var a = parseInt(selectMatches[2]); - var b = parseInt(fromMatches[2]); - var linesIntToStr = {}; - linesIntToStr[a] = selectMatches[2]; - linesIntToStr[b] = fromMatches[2]; - - var c; - if (a != b) { - if (a > b) { - c = a; - a = b; - b = c; + var selectTop = $select.offset().top; + var fromTop = $from.offset().top; + var hash; + + if (selectMatches[2] != fromMatches[2]) { + if ((selectTop > fromTop)) { + $startElem = $from; + $endElem = $select; + hash = fromMatches[1]+fromMatches[2] + '-' + selectMatches[2]; + } else { + $startElem = $select; + $endElem = $from; + hash = selectMatches[1]+selectMatches[2] + '-' + fromMatches[2]; } - $('[rel=diff-'+fromMatches[1]+'L' + linesIntToStr[b] + ']').parents('tr').next().addClass('end-selected-line'); - var $selectedLines = $('[rel=diff-'+fromMatches[1]+'L' + linesIntToStr[a] + ']').parents('tr').nextUntil('.end-selected-line').andSelf(); + $endElem.parents('tr').next().addClass('end-selected-line'); + var $selectedLines = $startElem.parents('tr').nextUntil('.end-selected-line').andSelf(); $selectedLines.find('td.lines-num > span').addClass('active') $selectedLines.find('td').addClass('selected-line'); - $.changeHash('#diff-'+fromMatches[1]+'L' + linesIntToStr[a] + '-L' + linesIntToStr[b]); + $.changeHash('#diff-'+hash); return } } @@ -262,7 +263,7 @@ var Gogs = {}; }); $(window).on('hashchange', function (e) { - var m = window.location.hash.match(/^#diff-(\d+)(L\d+)\-(L\d+)$/); + var m = window.location.hash.match(/^#diff-(\w+)([LR]\d+)\-([LR]\d+)$/); var $list = $('.code-diff td.lines-num > span'); var $first; if (m) { @@ -271,7 +272,7 @@ var Gogs = {}; $("html, body").scrollTop($first.offset().top - 200); return; } - m = window.location.hash.match(/^#diff-(\d+)(L\d+)$/); + m = window.location.hash.match(/^#diff-(\w+)([LR]\d+)$/); if (m) { $first = $list.filter('[rel=diff-' + m[1] + m[2] + ']'); selectRange($list, $first); diff --git a/public/ng/less/gogs/repository.less b/public/ng/less/gogs/repository.less index 59513c77..63c25d06 100644 --- a/public/ng/less/gogs/repository.less +++ b/public/ng/less/gogs/repository.less @@ -101,6 +101,7 @@ border-right: none; width: 190px; border-left: none; + cursor: default; } #repo-clone-help { clear: both; diff --git a/routers/admin/auths.go b/routers/admin/auths.go index e537572b..dcb98d33 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -63,15 +63,18 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { case models.LDAP: u = &models.LDAPConfig{ Ldapsource: ldap.Ldapsource{ - Host: form.Host, - Port: form.Port, - UseSSL: form.UseSSL, - BaseDN: form.BaseDN, - Attributes: form.Attributes, - Filter: form.Filter, - MsAdSAFormat: form.MsAdSA, - Enabled: true, - Name: form.AuthName, + Host: form.Host, + Port: form.Port, + UseSSL: form.UseSSL, + BaseDN: form.BaseDN, + AttributeUsername: form.AttributeUsername, + AttributeName: form.AttributeName, + AttributeSurname: form.AttributeSurname, + AttributeMail: form.AttributeMail, + Filter: form.Filter, + MsAdSAFormat: form.MsAdSA, + Enabled: true, + Name: form.AuthName, }, } case models.SMTP: @@ -142,15 +145,18 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { case models.LDAP: config = &models.LDAPConfig{ Ldapsource: ldap.Ldapsource{ - Host: form.Host, - Port: form.Port, - UseSSL: form.UseSSL, - BaseDN: form.BaseDN, - Attributes: form.Attributes, - Filter: form.Filter, - MsAdSAFormat: form.MsAdSA, - Enabled: true, - Name: form.AuthName, + Host: form.Host, + Port: form.Port, + UseSSL: form.UseSSL, + BaseDN: form.BaseDN, + AttributeUsername: form.AttributeUsername, + AttributeName: form.AttributeName, + AttributeSurname: form.AttributeSurname, + AttributeMail: form.AttributeMail, + Filter: form.Filter, + MsAdSAFormat: form.MsAdSA, + Enabled: true, + Name: form.AuthName, }, } case models.SMTP: diff --git a/routers/api/v1/repo.go b/routers/api/v1/repo.go index fbf9c73e..eb990891 100644 --- a/routers/api/v1/repo.go +++ b/routers/api/v1/repo.go @@ -5,7 +5,7 @@ package v1 import ( - "fmt" + "net/url" "path" "strings" @@ -156,17 +156,15 @@ func CreateOrgRepo(ctx *middleware.Context, opt api.CreateRepoOption) { func MigrateRepo(ctx *middleware.Context, form auth.MigrateRepoForm) { u, err := models.GetUserByName(ctx.Query("username")) if err != nil { - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) + if err == models.ErrUserNotExist { + ctx.HandleAPI(422, err) + } else { + ctx.HandleAPI(500, err) + } return } if !u.ValidtePassword(ctx.Query("password")) { - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": "username or password is not correct", - }) + ctx.HandleAPI(422, "Username or password is not correct.") return } @@ -175,56 +173,59 @@ func MigrateRepo(ctx *middleware.Context, form auth.MigrateRepoForm) { if form.Uid != u.Id { org, err := models.GetUserById(form.Uid) if err != nil { - log.Error(4, "GetUserById: %v", err) - ctx.Error(500) + if err == models.ErrUserNotExist { + ctx.HandleAPI(422, err) + } else { + ctx.HandleAPI(500, err) + } return } ctxUser = org } if ctx.HasError() { - ctx.JSON(422, map[string]interface{}{ - "ok": false, - "error": ctx.GetErrMsg(), - }) + ctx.HandleAPI(422, ctx.GetErrMsg()) return } if ctxUser.IsOrganization() { // Check ownership of organization. if !ctxUser.IsOwnedBy(u.Id) { - ctx.JSON(403, map[string]interface{}{ - "ok": false, - "error": "given user is not owner of organization", - }) + ctx.HandleAPI(403, "Given user is not owner of organization.") return } } - authStr := strings.Replace(fmt.Sprintf("://%s:%s", - form.AuthUserName, form.AuthPasswd), "@", "%40", -1) - url := strings.Replace(form.HttpsUrl, "://", authStr+"@", 1) - repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private, - form.Mirror, url) - if err == nil { - log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) - ctx.JSON(200, map[string]interface{}{ - "ok": true, - "data": "/" + ctxUser.Name + "/" + form.RepoName, - }) + // Remote address can be HTTPS URL or local path. + remoteAddr := form.CloneAddr + if strings.HasPrefix(form.CloneAddr, "http") { + u, err := url.Parse(form.CloneAddr) + if err != nil { + ctx.HandleAPI(422, err) + return + } + if len(form.AuthUserName) > 0 || len(form.AuthPasswd) > 0 { + u.User = url.UserPassword(form.AuthUserName, form.AuthPasswd) + } + remoteAddr = u.String() + } else if !com.IsDir(remoteAddr) { + ctx.HandleAPI(422, "Invalid local path, it does not exist or not a directory.") return } - if repo != nil { - if errDelete := models.DeleteRepository(ctxUser.Id, repo.Id, ctxUser.Name); errDelete != nil { - log.Error(4, "DeleteRepository: %v", errDelete) + repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private, form.Mirror, remoteAddr) + if err != nil { + if repo != nil { + if errDelete := models.DeleteRepository(ctxUser.Id, repo.Id, ctxUser.Name); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } } + ctx.HandleAPI(500, err) + return } - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) + log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) + ctx.WriteHeader(200) } // GET /user/repos @@ -237,28 +238,31 @@ func ListMyRepos(ctx *middleware.Context) { } numOwnRepos := len(ownRepos) - collaRepos, err := models.GetCollaborativeRepos(ctx.User.Name) + accessibleRepos, err := ctx.User.GetAccessibleRepositories() if err != nil { - ctx.JSON(500, &base.ApiJsonErr{"GetCollaborativeRepos: " + err.Error(), base.DOC_URL}) + ctx.JSON(500, &base.ApiJsonErr{"GetAccessibleRepositories: " + err.Error(), base.DOC_URL}) return } - repos := make([]*api.Repository, numOwnRepos+len(collaRepos)) + repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos)) for i := range ownRepos { repos[i] = ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true}) } - for i := range collaRepos { - if err = collaRepos[i].GetOwner(); err != nil { + i := numOwnRepos + + for repo, access := range accessibleRepos { + if err = repo.GetOwner(); err != nil { ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL}) return } - j := i + numOwnRepos - repos[j] = ToApiRepository(collaRepos[i].Owner, collaRepos[i].Repository, api.Permission{false, collaRepos[i].CanPush, true}) + + repos[i] = ToApiRepository(repo.Owner, repo, api.Permission{false, access >= models.ACCESS_MODE_WRITE, true}) // FIXME: cache result to reduce DB query? - if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOwnedBy(ctx.User.Id) { - repos[j].Permissions.Admin = true + if repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(ctx.User.Id) { + repos[i].Permissions.Admin = true } + i++ } ctx.JSON(200, &repos) diff --git a/routers/api/v1/repo_file.go b/routers/api/v1/repo_file.go index a049904f..73f97b2c 100644 --- a/routers/api/v1/repo_file.go +++ b/routers/api/v1/repo_file.go @@ -12,7 +12,7 @@ import ( ) func GetRepoRawFile(ctx *middleware.Context) { - if ctx.Repo.Repository.IsPrivate && !ctx.Repo.HasAccess { + if !ctx.Repo.HasAccess() { ctx.Error(404) return } diff --git a/routers/install.go b/routers/install.go index a3583a1a..4aa3ca93 100644 --- a/routers/install.go +++ b/routers/install.go @@ -189,6 +189,12 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) { // Save settings. cfg := ini.Empty() + if com.IsFile(setting.CustomConf) { + // Keeps custom settings if there is already something. + if err := cfg.Append(setting.CustomConf); err != nil { + log.Error(4, "Fail to load custom conf '%s': %v", setting.CustomConf, err) + } + } cfg.Section("database").Key("DB_TYPE").SetValue(models.DbCfg.Type) cfg.Section("database").Key("HOST").SetValue(models.DbCfg.Host) cfg.Section("database").Key("NAME").SetValue(models.DbCfg.Name) @@ -218,6 +224,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) { cfg.Section("session").Key("PROVIDER").SetValue("file") cfg.Section("log").Key("MODE").SetValue("file") + cfg.Section("log").Key("LEVEL").SetValue("Info") cfg.Section("security").Key("INSTALL_LOCK").SetValue("true") cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15)) diff --git a/routers/org/setting.go b/routers/org/setting.go index 41ec4a21..c638a032 100644 --- a/routers/org/setting.go +++ b/routers/org/setting.go @@ -39,18 +39,18 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateOrgSettingForm) { // Check if organization name has been changed. if org.Name != form.OrgUserName { - isExist, err := models.IsUserExist(form.OrgUserName) + isExist, err := models.IsUserExist(org.Id, form.OrgUserName) if err != nil { ctx.Handle(500, "IsUserExist", err) return } else if isExist { + ctx.Data["Err_UserName"] = true ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), SETTINGS_OPTIONS, &form) return } else if err = models.ChangeUserName(org, form.OrgUserName); err != nil { if err == models.ErrUserNameIllegal { - ctx.Flash.Error(ctx.Tr("form.illegal_username")) - ctx.Redirect(setting.AppSubUrl + "/org/" + org.LowerName + "/settings") - return + ctx.Data["Err_UserName"] = true + ctx.RenderWithErr(ctx.Tr("form.illegal_username"), SETTINGS_OPTIONS, &form) } else { ctx.Handle(500, "ChangeUserName", err) } @@ -68,7 +68,12 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateOrgSettingForm) { org.Avatar = base.EncodeMd5(form.Avatar) org.AvatarEmail = form.Avatar if err := models.UpdateUser(org); err != nil { - ctx.Handle(500, "UpdateUser", err) + if err == models.ErrEmailAlreadyUsed { + ctx.Data["Err_Email"] = true + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), SETTINGS_OPTIONS, &form) + } else { + ctx.Handle(500, "UpdateUser", err) + } return } log.Trace("Organization setting updated: %s", org.Name) diff --git a/routers/org/teams.go b/routers/org/teams.go index 9dd9b8e2..69f2734c 100644 --- a/routers/org/teams.go +++ b/routers/org/teams.go @@ -165,14 +165,14 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) { } // Validate permission level. - var auth models.AuthorizeType + var auth models.AccessMode switch form.Permission { case "read": - auth = models.ORG_READABLE + auth = models.ACCESS_MODE_READ case "write": - auth = models.ORG_WRITABLE + auth = models.ACCESS_MODE_WRITE case "admin": - auth = models.ORG_ADMIN + auth = models.ACCESS_MODE_ADMIN default: ctx.Error(401) return @@ -181,7 +181,7 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) { org := ctx.Org.Organization t := &models.Team{ - OrgId: org.Id, + OrgID: org.Id, Name: form.TeamName, Description: form.Description, Authorize: auth, @@ -246,14 +246,14 @@ func EditTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) { isAuthChanged := false if !t.IsOwnerTeam() { // Validate permission level. - var auth models.AuthorizeType + var auth models.AccessMode switch form.Permission { case "read": - auth = models.ORG_READABLE + auth = models.ACCESS_MODE_READ case "write": - auth = models.ORG_WRITABLE + auth = models.ACCESS_MODE_WRITE case "admin": - auth = models.ORG_ADMIN + auth = models.ACCESS_MODE_ADMIN default: ctx.Error(401) return diff --git a/routers/repo/http.go b/routers/repo/http.go index f5dc0c9d..3cfc0659 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -105,10 +105,10 @@ func Http(ctx *middleware.Context) { return } - authUser, err = models.GetUserByName(authUsername) + authUser, err := models.UserSignIn(authUsername, authPasswd) if err != nil { if err != models.ErrUserNotExist { - ctx.Handle(500, "GetUserByName", err) + ctx.Handle(500, "UserSignIn error: %v", err) return } @@ -128,27 +128,21 @@ func Http(ctx *middleware.Context) { return } authUsername = authUser.Name - } else { - // Check user's password when username is correctly presented. - if !authUser.ValidtePassword(authPasswd) { - ctx.Handle(401, "invalid password", nil) - return - } } if !isPublicPull { - var tp = models.WRITABLE + var tp = models.ACCESS_MODE_WRITE if isPull { - tp = models.READABLE + tp = models.ACCESS_MODE_READ } - has, err := models.HasAccess(authUsername, username+"/"+reponame, tp) + has, err := models.HasAccess(authUser, repo, tp) if err != nil { ctx.Handle(401, "no basic auth and digit auth", nil) return } else if !has { - if tp == models.READABLE { - has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE) + if tp == models.ACCESS_MODE_READ { + has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE) if err != nil || !has { ctx.Handle(401, "no basic auth and digit auth", nil) return @@ -158,6 +152,11 @@ func Http(ctx *middleware.Context) { return } } + + if !isPull && repo.IsMirror { + ctx.Handle(401, "can't push to mirror", nil) + return + } } } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 3e0206da..abe33beb 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -174,7 +174,7 @@ func CreateIssue(ctx *middleware.Context) { return } - us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) + us, err := ctx.Repo.Repository.GetCollaborators() if err != nil { ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) return @@ -218,7 +218,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { return } - _, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) + _, err = ctx.Repo.Repository.GetCollaborators() if err != nil { send(500, nil, err) return @@ -230,7 +230,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { } // Only collaborators can assign. - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { form.AssigneeId = 0 } issue := &models.Issue{ @@ -246,8 +246,8 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { if err := models.NewIssue(issue); err != nil { send(500, nil, err) return - } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id, - ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil { + } else if err := models.NewIssueUserPairs(ctx.Repo.Repository, issue.Id, ctx.Repo.Owner.Id, + ctx.User.Id, form.AssigneeId); err != nil { send(500, nil, err) return } @@ -384,7 +384,7 @@ func ViewIssue(ctx *middleware.Context) { } // Get all collaborators. - ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) + ctx.Data["Collaborators"], err = ctx.Repo.Repository.GetCollaborators() if err != nil { ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) return @@ -434,7 +434,7 @@ func ViewIssue(ctx *middleware.Context) { ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.Data["Comments"] = comments - ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) + ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner() || (ctx.IsSigned && issue.PosterId == ctx.User.Id) ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = false ctx.HTML(200, ISSUE_VIEW) @@ -457,7 +457,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) { return } - if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { + if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -484,7 +484,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) { } func UpdateIssueLabel(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -549,6 +549,7 @@ func UpdateIssueLabel(ctx *middleware.Context) { label.NumClosedIssues-- } } + if err = models.UpdateLabel(label); err != nil { ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err) return @@ -560,7 +561,7 @@ func UpdateIssueLabel(ctx *middleware.Context) { } func UpdateIssueMilestone(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -606,7 +607,7 @@ func UpdateIssueMilestone(ctx *middleware.Context) { } func UpdateAssignee(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -752,7 +753,7 @@ func Comment(ctx *middleware.Context) { // Check if issue owner changes the status of issue. var newStatus string - if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { + if ctx.Repo.IsOwner() || issue.PosterId == ctx.User.Id { newStatus = ctx.Query("change_status") } if len(newStatus) > 0 { @@ -767,6 +768,24 @@ func Comment(ctx *middleware.Context) { return } + if err = issue.GetLabels(); err != nil { + send(500, nil, err) + return + } + + for _, label := range issue.Labels { + if issue.IsClosed { + label.NumClosedIssues++ + } else { + label.NumClosedIssues-- + } + + if err = models.UpdateLabel(label); err != nil { + send(500, nil, err) + return + } + } + // Change open/closed issue counter for the associated milestone if issue.MilestoneId > 0 { if err = models.ChangeMilestoneIssueStats(issue); err != nil { diff --git a/routers/repo/release.go b/routers/repo/release.go index 591810cc..52d78b19 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -41,7 +41,7 @@ func Releases(ctx *middleware.Context) { tags := make([]*models.Release, len(rawTags)) for i, rawTag := range rawTags { for j, rel := range rels { - if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner) { + if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner()) { continue } if rel.TagName == rawTag { @@ -140,7 +140,7 @@ func Releases(ctx *middleware.Context) { } func NewRelease(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.ReleasesNew", nil) return } @@ -153,7 +153,7 @@ func NewRelease(ctx *middleware.Context) { } func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.ReleasesNew", nil) return } @@ -211,7 +211,7 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { } func EditRelease(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.ReleasesEdit", nil) return } @@ -234,7 +234,7 @@ func EditRelease(ctx *middleware.Context) { } func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.EditReleasePost", nil) return } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 48f7b09b..6b84a389 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -181,20 +181,26 @@ func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) { } } - u, err := url.Parse(form.HttpsUrl) - - if err != nil || u.Scheme != "https" { - ctx.Data["Err_HttpsUrl"] = true - ctx.RenderWithErr(ctx.Tr("form.url_error"), MIGRATE, &form) + // Remote address can be HTTPS URL or local path. + remoteAddr := form.CloneAddr + if strings.HasPrefix(form.CloneAddr, "http") { + u, err := url.Parse(form.CloneAddr) + if err != nil { + ctx.Data["Err_CloneAddr"] = true + ctx.RenderWithErr(ctx.Tr("form.url_error"), MIGRATE, &form) + return + } + if len(form.AuthUserName) > 0 || len(form.AuthPasswd) > 0 { + u.User = url.UserPassword(form.AuthUserName, form.AuthPasswd) + } + remoteAddr = u.String() + } else if !com.IsDir(remoteAddr) { + ctx.Data["Err_CloneAddr"] = true + ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), MIGRATE, &form) return } - if len(form.AuthUserName) > 0 || len(form.AuthPasswd) > 0 { - u.User = url.UserPassword(form.AuthUserName, form.AuthPasswd) - } - - repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private, - form.Mirror, u.String()) + repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private, form.Mirror, remoteAddr) if err == nil { log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + form.RepoName) @@ -343,7 +349,7 @@ func Action(ctx *middleware.Context) { case "unstar": err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, false) case "desc": - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(404) return } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 33bf1eab..5cd39ada 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -10,7 +10,6 @@ import ( "fmt" "strings" "time" - "path" "github.com/Unknwon/com" @@ -54,15 +53,11 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) { newRepoName := form.RepoName // Check if repository name has been changed. if ctx.Repo.Repository.Name != newRepoName { - isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) - if err != nil { - ctx.Handle(500, "IsRepositoryExist", err) - return - } else if isExist { + if models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) { ctx.Data["Err_RepoName"] = true ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil) 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 { if err == models.ErrRepoNameIllegal { ctx.Data["Err_RepoName"] = true ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), SETTINGS_OPTIONS, nil) @@ -84,7 +79,6 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) { ctx.Repo.Repository.Description = form.Description ctx.Repo.Repository.Website = form.Website ctx.Repo.Repository.IsPrivate = form.Private - ctx.Repo.Repository.IsGoget = form.GoGet if err := models.UpdateRepository(ctx.Repo.Repository); err != nil { ctx.Handle(404, "UpdateRepository", err) return @@ -110,7 +104,7 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) { } newOwner := ctx.Query("new_owner_name") - isExist, err := models.IsUserExist(newOwner) + isExist, err := models.IsUserExist(0, newOwner) if err != nil { ctx.Handle(500, "IsUserExist", err) return @@ -170,22 +164,12 @@ func SettingsCollaboration(ctx *middleware.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsCollaboration"] = true - repoLink := path.Join(ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName) - if ctx.Req.Method == "POST" { name := strings.ToLower(ctx.Query("collaborator")) if len(name) == 0 || ctx.Repo.Owner.LowerName == name { ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path) return } - has, err := models.HasAccess(name, repoLink, models.WRITABLE) - if err != nil { - ctx.Handle(500, "HasAccess", err) - return - } else if has { - ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path) - return - } u, err := models.GetUserByName(name) if err != nil { @@ -205,9 +189,8 @@ func SettingsCollaboration(ctx *middleware.Context) { return } - if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink, - Mode: models.WRITABLE}); err != nil { - ctx.Handle(500, "AddAccess", err) + if err = ctx.Repo.Repository.AddCollaborator(u); err != nil { + ctx.Handle(500, "AddCollaborator", err) return } @@ -226,50 +209,27 @@ func SettingsCollaboration(ctx *middleware.Context) { // Delete collaborator. remove := strings.ToLower(ctx.Query("remove")) if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { - needDelete := true - if ctx.User.IsOrganization() { - // Check if user belongs to a team that has access to this repository. - auth, err := models.GetHighestAuthorize(ctx.Repo.Owner.Id, ctx.User.Id, ctx.Repo.Repository.Id, 0) - if err != nil { - ctx.Handle(500, "GetHighestAuthorize", err) - return - } - if auth > 0 { - needDelete = false - } + u, err := models.GetUserByName(remove) + if err != nil { + ctx.Handle(500, "GetUserByName", err) + return } - - if needDelete { - if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil { - ctx.Handle(500, "DeleteAccess", err) - return - } + if err := ctx.Repo.Repository.DeleteCollaborator(u); err != nil { + ctx.Handle(500, "DeleteCollaborator", err) + return } ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") return } - names, err := models.GetCollaboratorNames(repoLink) + users, err := ctx.Repo.Repository.GetCollaborators() if err != nil { ctx.Handle(500, "GetCollaborators", err) return } - collaborators := make([]*models.User, 0, len(names)) - for _, name := range names { - u, err := models.GetUserByName(name) - if err != nil { - ctx.Handle(500, "GetUserByName", err) - return - } - // Does not show organization members. - if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgMember(u.Id) { - continue - } - collaborators = append(collaborators, u) - } - ctx.Data["Collaborators"] = collaborators + ctx.Data["Collaborators"] = users ctx.HTML(200, COLLABORATION) } diff --git a/routers/user/auth.go b/routers/user/auth.go index 9ed44e35..5dacaf8c 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -351,15 +351,12 @@ func ActivateEmail(ctx *middleware.Context) { // Verify code. if email := models.VerifyActiveEmailCode(code, email_string); email != nil { - err := email.Activate() - if err != nil { + if err := email.Activate(); err != nil { ctx.Handle(500, "ActivateEmail", err) } log.Trace("Email activated: %s", email.Email) - ctx.Flash.Success(ctx.Tr("settings.activate_email_success")) - } ctx.Redirect(setting.AppSubUrl + "/user/settings/email") diff --git a/routers/user/home.go b/routers/user/home.go index 1aabe087..0a1d9dd2 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -49,13 +49,19 @@ func Dashboard(ctx *middleware.Context) { } else { // Normal user. ctxUser = ctx.User - collaborates, err := models.GetCollaborativeRepos(ctxUser.Name) + collaborates, err := ctx.User.GetAccessibleRepositories() if err != nil { - ctx.Handle(500, "GetCollaborativeRepos", err) + ctx.Handle(500, "GetAccessibleRepositories", err) return } - ctx.Data["CollaborateCount"] = len(collaborates) - ctx.Data["CollaborativeRepos"] = collaborates + + repositories := make([]*models.Repository, 0, len(collaborates)) + for repo := range collaborates { + repositories = append(repositories, repo) + } + + ctx.Data["CollaborateCount"] = len(repositories) + ctx.Data["CollaborativeRepos"] = repositories } ctx.Data["ContextUser"] = ctxUser @@ -97,10 +103,14 @@ func Dashboard(ctx *middleware.Context) { feeds := make([]*models.Action, 0, len(actions)) for _, act := range actions { if act.IsPrivate { - if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName, - models.READABLE); !has { - continue + // This prevents having to retrieve the repository for each action + repo := &models.Repository{Id: act.RepoId, IsPrivate: true} + if act.RepoUserName != ctx.User.LowerName { + if has, _ := models.HasAccess(ctx.User, repo, models.ACCESS_MODE_READ); !has { + continue + } } + } // FIXME: cache results? u, err := models.GetUserByName(act.ActUserName) @@ -205,10 +215,14 @@ func Profile(ctx *middleware.Context) { if !ctx.IsSigned { continue } - if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName, - models.READABLE); !has { - continue + // This prevents having to retrieve the repository for each action + repo := &models.Repository{Id: act.RepoId, IsPrivate: true} + if act.RepoUserName != ctx.User.LowerName { + if has, _ := models.HasAccess(ctx.User, repo, models.ACCESS_MODE_READ); !has { + continue + } } + } // FIXME: cache results? u, err := models.GetUserByName(act.ActUserName) diff --git a/routers/user/setting.go b/routers/user/setting.go index 953e6113..a44d3b7e 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -50,7 +50,7 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateProfileForm) { // Check if user name has been changed. if ctx.User.Name != form.UserName { - isExist, err := models.IsUserExist(form.UserName) + isExist, err := models.IsUserExist(ctx.User.Id, form.UserName) if err != nil { ctx.Handle(500, "IsUserExist", err) return @@ -58,11 +58,14 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateProfileForm) { ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), SETTINGS_PROFILE, &form) return } else if err = models.ChangeUserName(ctx.User, form.UserName); err != nil { - if err == models.ErrUserNameIllegal { + switch err { + case models.ErrUserNameIllegal: ctx.Flash.Error(ctx.Tr("form.illegal_username")) ctx.Redirect(setting.AppSubUrl + "/user/settings") - return - } else { + case models.ErrEmailAlreadyUsed: + ctx.Flash.Error(ctx.Tr("form.email_been_used")) + ctx.Redirect(setting.AppSubUrl + "/user/settings") + default: ctx.Handle(500, "ChangeUserName", err) } return @@ -133,13 +136,12 @@ func SettingsEmails(ctx *middleware.Context) { ctx.Data["PageIsUserSettings"] = true ctx.Data["PageIsSettingsEmails"] = true - var err error - ctx.Data["Emails"], err = models.GetEmailAddresses(ctx.User.Id) - + emails, err := models.GetEmailAddresses(ctx.User.Id) if err != nil { - ctx.Handle(500, "email.GetEmailAddresses", err) + ctx.Handle(500, "GetEmailAddresses", err) return } + ctx.Data["Emails"] = emails ctx.HTML(200, SETTINGS_EMAILS) } @@ -149,16 +151,16 @@ func SettingsEmailPost(ctx *middleware.Context, form auth.AddEmailForm) { ctx.Data["PageIsUserSettings"] = true ctx.Data["PageIsSettingsEmails"] = true - var err error - ctx.Data["Emails"], err = models.GetEmailAddresses(ctx.User.Id) + emails, err := models.GetEmailAddresses(ctx.User.Id) if err != nil { - ctx.Handle(500, "email.GetEmailAddresses", err) + ctx.Handle(500, "GetEmailAddresses", err) return } + ctx.Data["Emails"] = emails - // Delete Email address. + // Delete E-mail address. if ctx.Query("_method") == "DELETE" { - id := com.StrTo(ctx.Query("id")).MustInt64() + id := ctx.QueryInt64("id") if id <= 0 { return } @@ -174,7 +176,7 @@ func SettingsEmailPost(ctx *middleware.Context, form auth.AddEmailForm) { // Make emailaddress primary. if ctx.Query("_method") == "PRIMARY" { - id := com.StrTo(ctx.Query("id")).MustInt64() + id := ctx.QueryInt64("id") if id <= 0 { return } @@ -189,46 +191,41 @@ func SettingsEmailPost(ctx *middleware.Context, form auth.AddEmailForm) { } // Add Email address. - if ctx.Req.Method == "POST" { - if ctx.HasError() { - ctx.HTML(200, SETTINGS_EMAILS) - return - } + if ctx.HasError() { + ctx.HTML(200, SETTINGS_EMAILS) + return + } - cleanEmail := strings.Replace(form.Email, "\n", "", -1) - e := &models.EmailAddress{ - Uid: ctx.User.Id, - Email: cleanEmail, - IsActivated: !setting.Service.RegisterEmailConfirm, - } + cleanEmail := strings.Replace(form.Email, "\n", "", -1) + e := &models.EmailAddress{ + Uid: ctx.User.Id, + Email: cleanEmail, + IsActivated: !setting.Service.RegisterEmailConfirm, + } - if err := models.AddEmailAddress(e); err != nil { - if err == models.ErrEmailAlreadyUsed { - ctx.RenderWithErr(ctx.Tr("form.email_has_been_used"), SETTINGS_EMAILS, &form) - return - } - ctx.Handle(500, "email.AddEmailAddress", err) + if err := models.AddEmailAddress(e); err != nil { + if err == models.ErrEmailAlreadyUsed { + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), SETTINGS_EMAILS, &form) return - } else { - - // Send confirmation e-mail - if setting.Service.RegisterEmailConfirm { - mailer.SendActivateEmail(ctx.Render, ctx.User, e) + } + ctx.Handle(500, "AddEmailAddress", err) + return + } else { + // Send confirmation e-mail + if setting.Service.RegisterEmailConfirm { + mailer.SendActivateEmail(ctx.Render, ctx.User, e) - if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil { - log.Error(4, "Set cache(MailResendLimit) fail: %v", err) - } - ctx.Flash.Success(ctx.Tr("settings.add_email_success_confirmation_email_sent")) - } else { - ctx.Flash.Success(ctx.Tr("settings.add_email_success")) + if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil { + log.Error(4, "Set cache(MailResendLimit) fail: %v", err) } - - log.Trace("Email address added: %s", e.Email) - - ctx.Redirect(setting.AppSubUrl + "/user/settings/email") - return + ctx.Flash.Success(ctx.Tr("settings.add_email_success_confirmation_email_sent")) + } else { + ctx.Flash.Success(ctx.Tr("settings.add_email_success")) } + log.Trace("Email address added: %s", e.Email) + ctx.Redirect(setting.AppSubUrl + "/user/settings/email") + return } ctx.HTML(200, SETTINGS_EMAILS) diff --git a/scripts/less.sh b/scripts/less.sh new file mode 100755 index 00000000..ff2f5736 --- /dev/null +++ b/scripts/less.sh @@ -0,0 +1,5 @@ +#!/bin/sh +echo "compiling LESS Files" +lessc ../public/ng/less/gogs.less ../public/ng/css/gogs.css +lessc ../public/ng/less/ui.less ../public/ng/css/ui.css +echo "done" diff --git a/templates/.VERSION b/templates/.VERSION index 8f04f6d0..0789cc30 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.5.13.0210 Beta
\ No newline at end of file +0.5.16.0228 Beta
\ No newline at end of file diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 77d28f62..e1bbd23d 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -48,8 +48,20 @@ <input class="ipt ipt-large ipt-radius {{if .Err_BaseDN}}ipt-error{{end}}" id="base_dn" name="base_dn" value="{{.Source.LDAP.BaseDN}}" /> </div> <div class="field"> - <label class="req" for="attributes">{{.i18n.Tr "admin.auths.attributes"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Attributes}}ipt-error{{end}}" id="attributes" name="attributes" value="{{.Source.LDAP.Attributes}}" /> + <label class="req" for="attribute_username">{{.i18n.Tr "admin.auths.attribute_username"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Attributes}}ipt-error{{end}}" id="attribute_username" name="attribute_username" value="{{.Source.LDAP.AttributeUsername}}" /> + </div> + <div class="field"> + <label class="req" for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Attributes}}ipt-error{{end}}" id="attribute_name" name="attribute_name" value="{{.Source.LDAP.AttributeName}}" /> + </div> + <div class="field"> + <label class="req" for="attribute_surname">{{.i18n.Tr "admin.auths.attribute_surname"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Attributes}}ipt-error{{end}}" id="attribute_surname" name="attribute_surname" value="{{.Source.LDAP.AttributeSurname}}" /> + </div> + <div class="field"> + <label class="req" for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Attributes}}ipt-error{{end}}" id="attribute_mail" name="attribute_mail" value="{{.Source.LDAP.AttributeMail}}" /> </div> <div class="field"> <label class="req" for="filter">{{.i18n.Tr "admin.auths.filter"}}</label> diff --git a/templates/admin/auth/list.tmpl b/templates/admin/auth/list.tmpl index aba516b8..ec701a8f 100644 --- a/templates/admin/auth/list.tmpl +++ b/templates/admin/auth/list.tmpl @@ -34,8 +34,8 @@ <td><a href="{{AppSubUrl}}/admin/auths/{{.Id}}">{{.Name}}</a></td> <td>{{.TypeString}}</td> <td><i class="fa fa{{if .IsActived}}-check{{end}}-square-o"></i></td> - <td><span title="{{DateFormat .Updated "r"}}">{{DateFormat .Updated "M d, Y"}}</span></td> - <td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td> + <td><span title="{{DateFmtLong .Updated}}">{{DateFmtShort .Updated}}</span></td> + <td><span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span></td> <td><a href="{{AppSubUrl}}/admin/auths/{{.Id}}"><i class="fa fa-pencil-square-o"></i></a></td> </tr> {{end}} diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 5cf84beb..6c328353 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -105,8 +105,8 @@ <dd>{{.Webhook.TaskInterval}} {{.i18n.Tr "tool.raw_minutes"}}</dd> <dt>{{.i18n.Tr "admin.config.deliver_timeout"}}</dt> <dd>{{.Webhook.DeliverTimeout}} {{.i18n.Tr "tool.raw_seconds"}}</dd> - <dt>{{.i18n.Tr "admin.config.allow_insecure_certification"}}</dt> - <dd><i class="fa fa{{if .Webhook.AllowInsecureCertification}}-check{{end}}-square-o"></i></dd> + <dt>{{.i18n.Tr "admin.config.skip_tls_verify"}}</dt> + <dd><i class="fa fa{{if .Webhook.SkipTLSVerify}}-check{{end}}-square-o"></i></dd> </dl> </div> </div> diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index b522dc08..ce5083a0 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -35,7 +35,7 @@ <td>{{.NumTeams}}</td> <td>{{.NumMembers}}</td> <td>{{.NumRepos}}</td> - <td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td> + <td><span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span></td> </tr> {{end}} </tbody> diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 88e16a43..981e2ef7 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -37,7 +37,7 @@ <td>{{.NumWatches}}</td> <td>{{.NumStars}}</td> <td>{{.NumIssues}}</td> - <td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td> + <td><span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span></td> </tr> {{end}} </tbody> diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index d42d5291..1dd5553e 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -37,7 +37,7 @@ <td><i class="fa fa{{if .IsActive}}-check{{end}}-square-o"></i></td> <td><i class="fa fa{{if .IsAdmin}}-check{{end}}-square-o"></i></td> <td>{{.NumRepos}}</td> - <td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td> + <td><span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created }}</span></td> <td><a href="{{AppSubUrl}}/admin/users/{{.Id}}"><i class="fa fa-pencil-square-o"></i></a></td> </tr> {{end}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 7775933c..cb3951ea 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -9,7 +9,7 @@ <meta name="description" content="Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language" /> <meta name="keywords" content="go, git"> <meta name="_csrf" content="{{.CsrfToken}}" /> - {{if .Repository.IsGoget}}<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">{{end}} + {{if .GoGetImport}}<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">{{end}} <!-- Stylesheets --> {{if CdnMode}} diff --git a/templates/ng/base/footer.tmpl b/templates/ng/base/footer.tmpl index e152a4de..89836fca 100644 --- a/templates/ng/base/footer.tmpl +++ b/templates/ng/base/footer.tmpl @@ -1,7 +1,7 @@ </div> <footer id="footer"> <div class="container clear"> - <p class="left" id="footer-rights">© 2015 GoGits · {{.i18n.Tr "version"}}: {{AppVer}} · {{.i18n.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> · + <p class="left" id="footer-rights">© 2015 Gogs · {{.i18n.Tr "version"}}: {{AppVer}} · {{.i18n.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> · {{.i18n.Tr "template"}}: <strong>{{call .TmplLoadTimes}}</strong></p> <div class="right" id="footer-links"> diff --git a/templates/ng/base/head.tmpl b/templates/ng/base/head.tmpl index 40a7d28f..f2a235bd 100644 --- a/templates/ng/base/head.tmpl +++ b/templates/ng/base/head.tmpl @@ -7,7 +7,7 @@ <meta name="description" content="Gogs(Go Git Service) a painless self-hosted Git Service written in Go" /> <meta name="keywords" content="go, git, self-hosted, gogs"> <meta name="_csrf" content="{{.CsrfToken}}" /> - {{if .Repository.IsGoget}}<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">{{end}} + {{if .GoGetImport}}<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">{{end}} <link rel="shortcut icon" href="{{AppSubUrl}}/img/favicon.png" /> diff --git a/templates/org/base/header.tmpl b/templates/org/base/header.tmpl index 1bbb092b..1649b920 100644 --- a/templates/org/base/header.tmpl +++ b/templates/org/base/header.tmpl @@ -2,7 +2,7 @@ <div class="container"> <a class="text-black left" href="{{AppSubUrl}}/org/{{.Org.LowerName}}"> <img class="avatar-48 left" src="{{.Org.AvatarLink}}?s=100"> - <span class="org-name">{{.Org.FullName}}</span> + <span class="org-name">{{if .Org.FullName}}{{.Org.FullName}}{{else}}{{.Org.Name}}{{end}}</span> </a> <ul class="menu menu-line container"> <li class="right"> diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index bb160b57..fdce06a2 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -9,85 +9,85 @@ {{if .IsOrganizationOwner}}<a class="text-grey" href="{{.OrgLink}}/settings"><span class="octicon octicon-gear"></span></a>{{end}} </h2> {{if .Org.Description}}<p>{{.Org.Description}}</p>{{end}} - <ul class="text-grey"> - {{if .Org.Location}}<li><span class="octicon octicon-location"></span> <span>{{.Org.Location}}</span></li>{{end}} - {{if .Org.Website}}<li><span class="octicon octicon-link"></span> <a target="_blank" href="{{.Org.Website}}">{{.Org.Website}}</a></li>{{end}} - {{if .Org.Email}}<li><span class="octicon octicon-mail"></span> <a href="mailto:{{.Org.Email}}">{{.Org.Email}}</a></li>{{end}} - </ul> + <ul class="text-grey"> + {{if .Org.Location}}<li><span class="octicon octicon-location"></span> <span>{{.Org.Location}}</span></li>{{end}} + {{if .Org.Website}}<li><span class="octicon octicon-link"></span> <a target="_blank" href="{{.Org.Website}}">{{.Org.Website}}</a></li>{{end}} + {{if .Org.Email}}<li><span class="octicon octicon-mail"></span> <a href="mailto:{{.Org.Email}}">{{.Org.Email}}</a></li>{{end}} + </ul> </div> </div> </div> <div class="container"> {{$isMember := .Org.IsOrgMember $.SignedUser.Id}} - <div id="org-home-repo-list" class="left grid-2-3"> - <div class="clear"> - {{if .IsOrganizationOwner}} - <a class="btn btn-green btn-large btn-link btn-radius right" href="{{AppSubUrl}}/repo/create?org={{.Org.Id}}"><i class="octicon octicon-repo-create"></i> {{.i18n.Tr "new_repo"}}</a> - {{end}} - </div> - <div id="org-repo-list"> - {{range .Repos}} - {{if .HasAccess $.SignedUser.Name}} - <div class="org-repo-item"> - <ul class="org-repo-status right"> - <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> - <li><i class="octicon octicon-git-branch"></i> {{.NumForks}}</li> - </ul> - <h2><a href="{{AppSubUrl}}/{{$.Org.Name}}/{{.Name}}">{{.Name}}</a></h2> - <p class="org-repo-description">{{.Description}}</p> - <p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> - </div> - {{end}} + <div id="org-home-repo-list" class="left grid-2-3"> + <div class="clear"> + {{if .IsOrganizationOwner}} + <a class="btn btn-green btn-large btn-link btn-radius right" href="{{AppSubUrl}}/repo/create?org={{.Org.Id}}"><i class="octicon octicon-repo-create"></i> {{.i18n.Tr "new_repo"}}</a> + {{end}} + </div> + <div id="org-repo-list"> + {{range .Repos}} + {{if or (not .IsPrivate) (.HasAccess $.SignedUser)}} + <div class="org-repo-item"> + <ul class="org-repo-status right"> + <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> + <li><i class="octicon octicon-git-branch"></i> {{.NumForks}}</li> + </ul> + <h2><a href="{{AppSubUrl}}/{{$.Org.Name}}/{{.Name}}">{{.Name}}</a></h2> + <p class="org-repo-description">{{.Description}}</p> + <p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> + </div> {{end}} - </div> + {{end}} </div> - <div class="grid-1-3 right"> - <div class="org-sidebar"> - <div class="panel panel-radius"> - <div class="panel-header"> - {{if $isMember}} - <a class="text-grey right" href="{{AppSubUrl}}/org/{{.Org.LowerName}}/members"><strong>{{.Org.NumMembers}}</strong><span class="octicon octicon-chevron-right"></span></a> - {{end}} - <strong>{{.i18n.Tr "org.people"}}</strong> - </div> - <div class="panel-body member-avatar-group"> - {{range .Members}} + </div> + <div class="grid-1-3 right"> + <div class="org-sidebar"> + <div class="panel panel-radius"> + <div class="panel-header"> + {{if $isMember}} + <a class="text-grey right" href="{{AppSubUrl}}/org/{{.Org.LowerName}}/members"><strong>{{.Org.NumMembers}}</strong><span class="octicon octicon-chevron-right"></span></a> + {{end}} + <strong>{{.i18n.Tr "org.people"}}</strong> + </div> + <div class="panel-body member-avatar-group"> + {{range .Members}} {{if or $isMember (.IsPublicMember $.Org.Id)}} - <a href="{{AppSubUrl}}/{{.Name}}" title="{{.Name}}"><img src="{{.AvatarLink}}"></a> - {{end}} + <a href="{{AppSubUrl}}/{{.Name}}" title="{{.Name}}"><img src="{{.AvatarLink}}"></a> {{end}} - </div> - {{if .IsOrganizationOwner}} - <div class="panel-footer"> - <a class="btn btn-medium btn-blue btn-link btn-radius" href="{{AppSubUrl}}/org/{{.Org.LowerName}}/invitations/new">{{.i18n.Tr "org.invite_someone"}}</a> - </div> - {{end}} - </div> - {{if $isMember}} - <br> - <div class="panel panel-radius"> - <div class="panel-header"> - <a class="text-grey right" href="{{AppSubUrl}}/org/{{.Org.LowerName}}/teams"><strong>{{.Org.NumTeams}}</strong><span class="octicon octicon-chevron-right"></span></a> - <strong>{{.i18n.Tr "org.teams"}}</strong> - </div> - <div class="panel-body" id="org-home-team-list"> - <ul> - {{range .Teams}} - <li> - <a class="text-black" href="{{AppSubUrl}}/org/{{$.Org.LowerName}}/teams/{{.LowerName}}"><strong class="team-name">{{.Name}}</strong></a> - <p class="team-meta">{{.NumMembers}} {{$.i18n.Tr "org.lower_members"}} · {{.NumRepos}} {{$.i18n.Tr "org.lower_repositories"}}</p> - </li> - {{end}} - </ul> - </div> - {{if .IsOrganizationOwner}} - <div class="panel-footer"> - <a class="btn btn-medium btn-blue btn-link btn-radius" href="{{AppSubUrl}}/org/{{$.Org.LowerName}}/teams/new">{{.i18n.Tr "org.create_new_team"}}</a> - </div> - {{end}} - </div> - {{end}} + {{end}} + </div> + {{if .IsOrganizationOwner}} + <div class="panel-footer"> + <a class="btn btn-medium btn-blue btn-link btn-radius" href="{{AppSubUrl}}/org/{{.Org.LowerName}}/invitations/new">{{.i18n.Tr "org.invite_someone"}}</a> + </div> + {{end}} </div> - </div> + {{if $isMember}} + <br> + <div class="panel panel-radius"> + <div class="panel-header"> + <a class="text-grey right" href="{{AppSubUrl}}/org/{{.Org.LowerName}}/teams"><strong>{{.Org.NumTeams}}</strong><span class="octicon octicon-chevron-right"></span></a> + <strong>{{.i18n.Tr "org.teams"}}</strong> + </div> + <div class="panel-body" id="org-home-team-list"> + <ul> + {{range .Teams}} + <li> + <a class="text-black" href="{{AppSubUrl}}/org/{{$.Org.LowerName}}/teams/{{.LowerName}}"><strong class="team-name">{{.Name}}</strong></a> + <p class="team-meta">{{.NumMembers}} {{$.i18n.Tr "org.lower_members"}} · {{.NumRepos}} {{$.i18n.Tr "org.lower_repositories"}}</p> + </li> + {{end}} + </ul> + </div> + {{if .IsOrganizationOwner}} + <div class="panel-footer"> + <a class="btn btn-medium btn-blue btn-link btn-radius" href="{{AppSubUrl}}/org/{{$.Org.LowerName}}/teams/new">{{.i18n.Tr "org.create_new_team"}}</a> + </div> + {{end}} + </div> + {{end}} + </div> + </div> </div> {{template "ng/base/footer" .}}
\ No newline at end of file diff --git a/templates/org/settings/nav.tmpl b/templates/org/settings/nav.tmpl index 11d32d7f..1285c4ab 100644 --- a/templates/org/settings/nav.tmpl +++ b/templates/org/settings/nav.tmpl @@ -1,12 +1,12 @@ <div id="setting-menu" class="grid-1-5 panel panel-radius left"> - <div class="panel-header"> - <strong>{{.i18n.Tr "org.settings"}}</strong> - </div> - <div class="panel-body"> - <ul class="menu menu-vertical switching-list grid-1-5 left"> - <li {{if .PageIsSettingsOptions}}class="current"{{end}}><a href="{{AppSubUrl}}/org/{{.Org.LowerName}}/settings">{{.i18n.Tr "org.settings.options"}}</a></li> - <li {{if .PageIsSettingsHooks}}class="current"{{end}}><a href="{{AppSubUrl}}/org/{{.Org.LowerName}}/settings/hooks">{{.i18n.Tr "repo.settings.hooks"}}</a></li> - <li {{if .PageIsSettingsDelete}}class="current"{{end}}><a href="{{AppSubUrl}}/org/{{.Org.LowerName}}/settings/delete">{{.i18n.Tr "org.settings.delete"}}</a></li> - </ul> - </div> + <div class="panel-header"> + <strong>{{.i18n.Tr "org.settings"}}</strong> + </div> + <div class="panel-body"> + <ul class="menu menu-vertical switching-list grid-1-5 left"> + <li {{if .PageIsSettingsOptions}}class="current"{{end}}><a href="{{AppSubUrl}}/org/{{.Org.Name}}/settings">{{.i18n.Tr "org.settings.options"}}</a></li> + <li {{if .PageIsSettingsHooks}}class="current"{{end}}><a href="{{AppSubUrl}}/org/{{.Org.Name}}/settings/hooks">{{.i18n.Tr "repo.settings.hooks"}}</a></li> + <li {{if .PageIsSettingsDelete}}class="current"{{end}}><a href="{{AppSubUrl}}/org/{{.Org.Name}}/settings/delete">{{.i18n.Tr "org.settings.delete"}}</a></li> + </ul> + </div> </div> diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 1ed7acb5..1179ede6 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -2,63 +2,63 @@ {{template "ng/base/header" .}} {{template "org/base/header" .}} <div id="setting-wrapper" class="main-wrapper"> - <div id="org-setting" class="container clear"> - {{template "org/settings/nav" .}} - <div class="grid-4-5 left"> - <div class="setting-content"> - {{template "ng/base/alert" .}} - <div id="setting-content"> - <div id="user-profile-setting-content" class="panel panel-radius"> - <div class="panel-header"> - <strong>{{.i18n.Tr "org.settings.options"}}</strong> - </div> - <form class="form form-align panel-body" id="org-setting-form" action="{{AppSubUrl}}/org/{{.Org.LowerName}}/settings" method="post"> - {{.CsrfTokenHtml}} - <input type="hidden" name="action" value="update"> - <div class="field"> - <label class="req" for="orgname">{{.i18n.Tr "username"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="orgname" name="uname" value="{{.Org.Name}}" data-orgname="{{.Org.Name}}" required /> - </div> - <div class="white-popup-block mfp-hide" id="change-orgname-modal"> - <h1 class="text-red">{{.i18n.Tr "org.settings.change_orgname"}}</h1> - <p>{{.i18n.Tr "org.settings.change_orgname_desc"}}</p> - <br> - <button class="btn btn-red btn-large btn-radius" id="change-orgname-submit">{{.i18n.Tr "settings.continue"}}</button> - <button class="btn btn-large btn-radius popup-modal-dismiss">{{.i18n.Tr "settings.cancel"}}</button> - </div> - <div class="field"> - <label for="full-name">{{.i18n.Tr "org.settings.full_name"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_FullName}}ipt-error{{end}}" id="full-name" name="fullname" value="{{.Org.FullName}}" /> - </div> - <div class="field"> - <label class="req" for="email">{{.i18n.Tr "email"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Email}}ipt-error{{end}}" id="email" name="email" type="email" value="{{.Org.Email}}" required /> - </div> - <div class="field clear"> - <label class="left" for="desc">{{.i18n.Tr "org.org_desc"}}</label> - <textarea class="ipt ipt-large ipt-radius {{if .Err_Description}}ipt-error{{end}}" id="desc" name="desc">{{.Org.Description}}</textarea> - </div> - <div class="field"> - <label for="website">{{.i18n.Tr "org.settings.website"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Website}}ipt-error{{end}}" id="website" name="website" type="url" value="{{.Org.Website}}" /> - </div> - <div class="field"> - <label for="location">{{.i18n.Tr "org.settings.location"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Location}}ipt-error{{end}}" id="location" name="location" type="text" value="{{.Org.Location}}" /> - </div> - <div class="field"> - <label for="gravatar-email">Gravatar {{.i18n.Tr "email"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Avatar}}ipt-error{{end}}" id="gravatar-email" name="avatar" type="text" value="{{.Org.AvatarEmail}}" /> - </div> - <div class="field"> - <span class="form-label"></span> - <button class="btn btn-green btn-large btn-radius" id="change-orgname-btn" href="#change-orgname-modal">{{.i18n.Tr "org.settings.update_settings"}}</button> - </div> - </form> - </div> - </div> + <div id="org-setting" class="container clear"> + {{template "org/settings/nav" .}} + <div class="grid-4-5 left"> + <div class="setting-content"> + {{template "ng/base/alert" .}} + <div id="setting-content"> + <div id="user-profile-setting-content" class="panel panel-radius"> + <div class="panel-header"> + <strong>{{.i18n.Tr "org.settings.options"}}</strong> </div> - </div> - </div> + <form class="form form-align panel-body" id="org-setting-form" action="{{AppSubUrl}}/org/{{.Org.LowerName}}/settings" method="post"> + {{.CsrfTokenHtml}} + <input type="hidden" name="action" value="update"> + <div class="field"> + <label class="req" for="orgname">{{.i18n.Tr "username"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="orgname" name="uname" value="{{.Org.Name}}" data-orgname="{{.Org.Name}}" required /> + </div> + <div class="white-popup-block mfp-hide" id="change-orgname-modal"> + <h1 class="text-red">{{.i18n.Tr "org.settings.change_orgname"}}</h1> + <p>{{.i18n.Tr "org.settings.change_orgname_desc"}}</p> + <br> + <button class="btn btn-red btn-large btn-radius" id="change-orgname-submit">{{.i18n.Tr "settings.continue"}}</button> + <button class="btn btn-large btn-radius popup-modal-dismiss">{{.i18n.Tr "settings.cancel"}}</button> + </div> + <div class="field"> + <label for="full-name">{{.i18n.Tr "org.settings.full_name"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_FullName}}ipt-error{{end}}" id="full-name" name="fullname" value="{{.Org.FullName}}" /> + </div> + <div class="field"> + <label class="req" for="email">{{.i18n.Tr "email"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Email}}ipt-error{{end}}" id="email" name="email" type="email" value="{{.Org.Email}}" required /> + </div> + <div class="field clear"> + <label class="left" for="desc">{{.i18n.Tr "org.org_desc"}}</label> + <textarea class="ipt ipt-large ipt-radius {{if .Err_Description}}ipt-error{{end}}" id="desc" name="desc">{{.Org.Description}}</textarea> + </div> + <div class="field"> + <label for="website">{{.i18n.Tr "org.settings.website"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Website}}ipt-error{{end}}" id="website" name="website" type="url" value="{{.Org.Website}}" /> + </div> + <div class="field"> + <label for="location">{{.i18n.Tr "org.settings.location"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Location}}ipt-error{{end}}" id="location" name="location" type="text" value="{{.Org.Location}}" /> + </div> + <div class="field"> + <label for="gravatar-email">Gravatar {{.i18n.Tr "email"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Avatar}}ipt-error{{end}}" id="gravatar-email" name="avatar" type="text" value="{{.Org.AvatarEmail}}" /> + </div> + <div class="field"> + <span class="form-label"></span> + <button class="btn btn-green btn-large btn-radius" id="change-orgname-btn" href="#change-orgname-modal">{{.i18n.Tr "org.settings.update_settings"}}</button> + </div> + </form> + </div> + </div> + </div> + </div> + </div> </div> {{template "ng/base/footer" .}}
\ No newline at end of file diff --git a/templates/repo/bare.tmpl b/templates/repo/bare.tmpl index 2a1409a6..c050b623 100644 --- a/templates/repo/bare.tmpl +++ b/templates/repo/bare.tmpl @@ -23,7 +23,7 @@ <h2>{{.i18n.Tr "repo.clone_this_repo"}}</h2> <button class="btn btn-blue current left btn-left-radius" id="repo-clone-ssh" data-link="{{.CloneLink.SSH}}">SSH</button> <button class="btn btn-gray left" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">HTTPS</button> - <input id="repo-clone-url" type="text" class="ipt ipt-disabled left" value="{{.CloneLink.SSH}}" readonly /> + <input id="repo-clone-url" type="text" class="ipt ipt-disabled left" value="{{.CloneLink.SSH}}" onclick="this.select()" readonly /> <button class="btn btn-black left btn-right-radius" id="repo-clone-copy" data-copy-val="val" data-copy-from="#repo-clone-url">{{.i18n.Tr "repo.copy_link"}}</button> <p class="text-center" id="repo-clone-help">{{.i18n.Tr "repo.clone_helper" | Str2html}}</p> <hr/> @@ -50,4 +50,4 @@ git push -u origin master</code></pre> </div> </div> </div> -{{template "ng/base/footer" .}}
\ No newline at end of file +{{template "ng/base/footer" .}} diff --git a/templates/repo/diff.tmpl b/templates/repo/diff.tmpl index 443e002d..f261da55 100644 --- a/templates/repo/diff.tmpl +++ b/templates/repo/diff.tmpl @@ -105,14 +105,14 @@ {{else}} <table> <tbody> - {{range $j, $section := $file.Sections}} - {{range $k, $line := $section.Lines}} - <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$i}} ol-{{$i}}"> + {{range .Sections}} + {{range $k, $line := .Lines}} + <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> <td class="lines-num lines-num-old"> - <span rel="diff-{{Add $i 1}}L{{$j}}{{$k}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> + <span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> </td> <td class="lines-num lines-num-new"> - <span rel="diff-{{Add $i 1}}L{{$j}}{{$k}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span> + <span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span> </td> <td class="lines-code"> diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 9e52efc7..21f9cea8 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -22,7 +22,7 @@ <button class="btn btn-blue left btn-left-radius" id="repo-clone-ssh" data-link="{{$.CloneLink.SSH}}">SSH</button> {{end}} <button class="btn {{if $.DisableSSH}}btn-blue{{else}}btn-gray{{end}} left" id="repo-clone-https" data-link="{{$.CloneLink.HTTPS}}">HTTPS</button> - <input id="repo-clone-url" class="ipt ipt-disabled left" value="{{if $.DisableSSH}}{{$.CloneLink.HTTPS}}{{else}}{{$.CloneLink.SSH}}{{end}}" readonly /> + <input id="repo-clone-url" class="ipt ipt-disabled left" value="{{if $.DisableSSH}}{{$.CloneLink.HTTPS}}{{else}}{{$.CloneLink.SSH}}{{end}}" onclick="this.select();" readonly /> <button id="repo-clone-copy" class="btn btn-black left btn-right-radius" data-copy-val="val" data-copy-from="#repo-clone-url" original-title="{{$.i18n.Tr "repo.click_to_copy"}}" data-original-title="{{$.i18n.Tr "repo.click_to_copy"}}" data-after-title="{{$.i18n.Tr "repo.copied"}}">{{$.i18n.Tr "repo.copy_link"}}</button> <p class="text-center" id="repo-clone-help">{{$.i18n.Tr "repo.clone_helper" "http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository" | Str2html}}</p> <hr/> @@ -49,7 +49,7 @@ </a> </li> <li id="repo-header-fork"> - <a id="repo-header-fork-btn" {{if or (not $.IsRepositoryTrueOwner) $.Owner.IsOrganization}}href="{{AppSubUrl}}/repo/fork?fork_id={{.Id}}"{{end}}> + <a id="repo-header-fork-btn" {{if or (not $.IsRepositoryAdmin) $.Owner.IsOrganization}}href="{{AppSubUrl}}/repo/fork?fork_id={{.Id}}"{{end}}> <button class="btn btn-gray text-bold btn-radius"> <i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}} <span class="num">{{.NumForks}}</span> @@ -60,4 +60,4 @@ </ul> </div> </div> -{{end}}
\ No newline at end of file +{{end}} diff --git a/templates/repo/migrate.tmpl b/templates/repo/migrate.tmpl index b28d0647..5869be15 100644 --- a/templates/repo/migrate.tmpl +++ b/templates/repo/migrate.tmpl @@ -7,8 +7,8 @@ <div class="panel-content"> {{template "ng/base/alert" .}} <div class="field"> - <label class="req" for="url">HTTPS URL</label> - <input class="ipt ipt-large ipt-radius {{if .Err_HttpsUrl}}ipt-error{{end}}" id="url" name="url" type="text" value="{{.url}}" required /> + <label class="req" for="clone_addr">{{.i18n.Tr "repo.migrate.clone_address"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_CloneAddr}}ipt-error{{end}}" id="clone_addr" name="clone_addr" type="text" value="{{.clone_addr}}" required /> </div> <div class="field"> <span class="form-label"></span> diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 093e9375..41683f84 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -59,11 +59,6 @@ <input class="ipt-chk" id="visibility" name="private" type="checkbox" {{if .Repository.IsPrivate}}checked{{end}} /> <span>{{.i18n.Tr "repo.visiblity_helper" | Str2html}}</span> </div> - <div class="field"> - <label for="goget">{{.i18n.Tr "repo.goget_meta"}}</label> - <input class="ipt-chk" id="goget" name="goget" type="checkbox" {{if .Repository.IsGoget}}checked{{end}} /> - <span>{{.i18n.Tr "repo.goget_meta_helper" | Str2html}}</span> - </div> <div class="field"> <span class="form-label"></span> <button class="btn btn-green btn-large btn-radius" id="change-reponame-btn" href="#change-reponame-modal">{{.i18n.Tr "repo.settings.update_settings"}}</button> diff --git a/templates/repo/sidebar.tmpl b/templates/repo/sidebar.tmpl index 9d6abb47..16096038 100644 --- a/templates/repo/sidebar.tmpl +++ b/templates/repo/sidebar.tmpl @@ -20,7 +20,7 @@ <!-- <li> <a class="radius" href="#"><i class="octicon octicon-organization"></i>contributors <span class="num right label label-gray label-radius">43</span></a> </li> --> - {{if .IsRepositoryTrueOwner}} + {{if .IsRepositoryAdmin}} <li class="border-bottom"></li> <li> <a class="radius" href="{{.RepoLink}}/settings"><i class="octicon octicon-tools"></i>{{.i18n.Tr "repo.settings"}}</a> diff --git a/templates/repo/toolbar.tmpl b/templates/repo/toolbar.tmpl index 6357b3c4..f2254d21 100644 --- a/templates/repo/toolbar.tmpl +++ b/templates/repo/toolbar.tmpl @@ -35,7 +35,7 @@ <li><a href="#">Pulse</a></li> <li><a href="#">Network</a></li> </ul> - </li> -->{{end}}{{if .IsRepositoryTrueOwner}} + </li> -->{{end}}{{if .IsRepositoryAdmin}} <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="{{.RepoLink}}/settings">Settings</a> </li>{{end}} </ul> diff --git a/templates/user/auth/signin.tmpl b/templates/user/auth/signin.tmpl index 78d6febb..455df63a 100644 --- a/templates/user/auth/signin.tmpl +++ b/templates/user/auth/signin.tmpl @@ -26,10 +26,12 @@ <button class="btn btn-green btn-large btn-radius">{{.i18n.Tr "sign_in"}}</button> {{if not .IsSocialLogin}}<a href="{{AppSubUrl}}/user/forget_password">{{.i18n.Tr "auth.forget_password"}}</a>{{end}} </div> + {{if .ShowRegistrationButton}} <div class="field"> <label></label> <a href="{{AppSubUrl}}/user/sign_up">{{.i18n.Tr "auth.sign_up_now" | Str2html}}</a> </div> + {{end}} {{if and (not .IsSocialLogin) .OauthEnabled}} <hr/> <div id="sign-social" class="text-center social-buttons"> diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 44c22123..dab90c35 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -28,7 +28,7 @@ {{if .Owner.Website}} <li class="list-group-item"><i class="octicon octicon-link"></i> <a target="_blank" href="{{.Owner.Website}}">{{.Owner.Website}}</a></li> {{end}} - <li class="list-group-item"><i class="octicon octicon-clock"></i> {{.i18n.Tr "user.join_on"}} {{DateFormat .Owner.Created "M d, Y"}}</li> + <li class="list-group-item"><i class="octicon octicon-clock"></i> {{.i18n.Tr "user.join_on"}} {{DateFmtShort .Owner.Created}}</li> </ul> <hr> <ul class="list-no-style"> @@ -74,7 +74,7 @@ <div class="tab-pane active"> <div id="org-repo-list"> {{range .Repos}} - {{if or (not .IsPrivate) (.HasAccess $.SignedUserName)}} + {{if or (not .IsPrivate) (.HasAccess $.SignedUser)}} <div class="org-repo-item"> <ul class="org-repo-status right"> <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index ce74ef77..2e766a3d 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -22,7 +22,7 @@ <i class="fa fa-send fa-2x left"></i> <div class="ssh-content left"> <p><strong>{{.Name}}</strong></p> - <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p> + <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFmtShort .Updated}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p> </div> <a href="{{AppSubUrl}}/user/settings/applications?remove={{.Id}}"> <button class="btn btn-small btn-red btn-radius ssh-btn right">{{$.i18n.Tr "settings.delete_token"}}</button> diff --git a/templates/user/settings/email.tmpl b/templates/user/settings/email.tmpl index c99e6a04..ec152c5d 100644 --- a/templates/user/settings/email.tmpl +++ b/templates/user/settings/email.tmpl @@ -16,7 +16,7 @@ {{range .Emails}} <li class="email clear"> <div class="email-content left"> - <p><strong>{{.Email}}</strong> {{if .IsPrimary}} <span class="email-primary">{{$.i18n.Tr "settings.primary"}}</span> {{end}}</p> + <p><strong>{{.Email}}</strong> {{if .IsPrimary}} <span class="text-red">{{$.i18n.Tr "settings.primary"}}</span> {{end}}</p> </div> {{if not .IsPrimary}} {{if .IsActivated}} @@ -24,14 +24,14 @@ {{$.CsrfTokenHtml}} <input name="_method" type="hidden" value="PRIMARY"> <input name="id" type="hidden" value="{{.Id}}"> - <button class="right email-btn btn btn-green btn-radius btn-small">{{$.i18n.Tr "settings.primary_email"}}</button> + <button class="right email-btn btn btn-small btn-green btn-radius">{{$.i18n.Tr "settings.primary_email"}}</button> </form> {{end}} <form action="{{AppSubUrl}}/user/settings/email" method="post"> {{$.CsrfTokenHtml}} <input name="_method" type="hidden" value="DELETE"> <input name="id" type="hidden" value="{{.Id}}"> - <button class="right email-btn btn btn-red btn-radius btn-small">{{$.i18n.Tr "settings.delete_email"}}</button> + <button class="right email-btn btn btn-small btn-red btn-radius" style="margin-right: 5px">{{$.i18n.Tr "settings.delete_email"}}</button> </form> {{end}} </li> diff --git a/templates/user/settings/social.tmpl b/templates/user/settings/social.tmpl index b47f883e..f2a30da7 100644 --- a/templates/user/settings/social.tmpl +++ b/templates/user/settings/social.tmpl @@ -18,7 +18,7 @@ <div class="ssh-content left"> <p><strong>{{Oauth2Name .Type}}</strong></p> <p class="print">{{.Identity}}</p> - <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span> — <i class="octicon octicon-info"></i>{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}</i></p> + <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span> — <i class="octicon octicon-info"></i>{{$.i18n.Tr "settings.last_used"}} {{DateFmtShort .Updated}}</i></p> </div> <a class="right btn btn-small btn-red btn-header btn-radius" href="{{AppSubUrl}}/user/settings/social?remove={{.Id}}">{{$.i18n.Tr "settings.unbind"}}</a> </li> diff --git a/templates/user/settings/sshkeys.tmpl b/templates/user/settings/sshkeys.tmpl index 48a4d343..42b76039 100644 --- a/templates/user/settings/sshkeys.tmpl +++ b/templates/user/settings/sshkeys.tmpl @@ -23,7 +23,7 @@ <div class="ssh-content left"> <p><strong>{{.Name}}</strong></p> <p class="print">{{.Fingerprint}}</p> - <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span title="{{DateFormat .Updated "r"}}">{{DateFormat .Updated "M d, Y"}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p> + <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFmtLong .Created}}">{{DateFmtShort .Created}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span title="{{DateFmtLong .Updated}}">{{DateFmtShort .Updated}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p> </div> <form action="{{AppSubUrl}}/user/settings/ssh" method="post"> {{$.CsrfTokenHtml}} |