diff --git a/.gitignore b/.gitignore
index 3e550c3fc..ad27cc8be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@ gogs
*.db
*.log
custom/
-.vendor/
\ No newline at end of file
+.vendor/
+.idea/
+*.iml
\ No newline at end of file
diff --git a/.gopmfile b/.gopmfile
index 9e2440f3e..5b690a06a 100644
--- a/.gopmfile
+++ b/.gopmfile
@@ -9,13 +9,12 @@ github.com/Unknwon/com=
github.com/Unknwon/cae=
github.com/Unknwon/goconfig=
github.com/dchest/scrypt=
-github.com/go-sql-driver/mysql=
-github.com/lib/pq=
github.com/lunny/xorm=
github.com/gogits/logs=
github.com/gogits/binding=
github.com/gogits/git=
github.com/gogits/gfm=
+github.com/gogits/cache=
[res]
include=templates|public|conf
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..08013d370
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,49 @@
+# Contributing to Gogs
+
+Want to hack on Gogs? Awesome! Here are instructions to get you
+started. They are probably not perfect, please let us know if anything
+feels wrong or incomplete.
+
+## Contribution guidelines
+
+### Pull requests are always welcome
+
+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.
+
+If your pull request is not accepted on the first try, don't be
+discouraged! If there's a problem with the implementation, hopefully you
+received feedback on what to improve.
+
+We're trying very hard to keep Gogs lean and focused. We don't want it
+to do everything for everybody. This means that we might decide against
+incorporating a new feature.
+
+### Discuss your design on the mailing list
+
+We recommend discussing your plans [on the mailing
+list](https://groups.google.com/forum/#!forum/gogits)
+before starting to code - especially for more ambitious contributions.
+This gives other contributors a chance to point you in the right
+direction, give feedback on your design, and maybe point out if someone
+else is working on the same thing.
+
+We may close your pull request if not first discussed on the mailing
+list. We aren't doing this to be jerks. We are doing this to prevent
+people from spending large amounts of time on changes that may need
+to be designed or architected in a specific way, or may not align with
+the vision of the project.
+
+### Create issues...
+
+Any significant improvement should be documented as [a GitHub
+issue](https://github.com/gogits/gogs/issues) before anybody
+starts working on it.
+
+### ...but check for existing issues first!
+
+Please take a moment to check that an issue doesn't already exist
+documenting your bug report or improvement proposal. If it does, it
+never hurts to add a quick "+1" or "I have this problem too". This will
+help prioritize the most common problems and requests.
\ No newline at end of file
diff --git a/README.md b/README.md
index 3a1023c6d..cbd1f588d 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language.
Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Max OS X, and Windows with **ZERO** dependency.
-##### Current version: 0.1.1 Alpha
+##### Current version: 0.1.5 Alpha
## Purpose
@@ -15,7 +15,7 @@ There are some very good products in this category such as [gitlab](http://gitla
- Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, develop specification, change log and road map.
- See [Trello Broad](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
-- Try it before anything? Go down to **Installation -> Install from binary** section!.
+- Try it before anything? Do it [online](http://try.gogits.org/Unknown/gogs) or go down to **Installation -> Install from binary** section!
- Having troubles? Get help from [Troubleshooting](https://github.com/gogits/gogs/wiki/Troubleshooting).
## Features
@@ -28,7 +28,8 @@ There are some very good products in this category such as [gitlab](http://gitla
- Repository viewer.
- Gravatar support.
- Mail service(register).
-- Supports MySQL and PostgreSQL.
+- Administration panel.
+- Supports MySQL, PostgreSQL and SQLite3(binary release only).
## Installation
diff --git a/conf/app.ini b/conf/app.ini
index 658f7c015..ecb0d2511 100644
--- a/conf/app.ini
+++ b/conf/app.ini
@@ -8,8 +8,8 @@ RUN_MODE = dev
[repository]
ROOT = /Users/%(RUN_USER)s/git/gogs-repositories
-LANG_IGNS = Google Go|C|Python|Ruby|C Sharp
-LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|BSD (3-Clause) License
+LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp
+LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
[server]
DOMAIN = localhost
@@ -18,7 +18,7 @@ HTTP_ADDR =
HTTP_PORT = 3000
[database]
-; Either "mysql" or "postgres", it's your choice
+; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice
DB_TYPE = mysql
HOST =
NAME = gogs
@@ -26,6 +26,10 @@ USER = root
PASSWD =
; For "postgres" only, either "disable", "require" or "verify-full"
SSL_MODE = disable
+; For "sqlite3" only
+PATH = data/gogs.db
+
+[admin]
[security]
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
@@ -36,6 +40,10 @@ ACTIVE_CODE_LIVE_MINUTES = 180
RESET_PASSWD_CODE_LIVE_MINUTES = 180
; User need to confirm e-mail for registration
REGISTER_EMAIL_CONFIRM = false
+; Does not allow register and admin create account only
+DISENABLE_REGISTERATION = false
+; User must sign in to view anything.
+REQUIRE_SIGNIN_VIEW = false
[mailer]
ENABLED = false
@@ -52,6 +60,16 @@ FROM =
USER =
PASSWD =
+[cache]
+; Either "memory", "redis", or "memcache", default is "memory"
+ADAPTER = memory
+; For "memory" only, GC interval in seconds, default is 60
+INTERVAL = 60
+; For "redis" and "memcache", connection host address
+; redis: ":6039"
+; memcache: "127.0.0.1:11211"
+HOST =
+
[log]
; Either "console", "file", "conn" or "smtp", default is "console"
MODE = console
diff --git a/conf/gitignore/C++ b/conf/gitignore/C++
new file mode 100644
index 000000000..5a1b6ec43
--- /dev/null
+++ b/conf/gitignore/C++
@@ -0,0 +1,13 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
\ No newline at end of file
diff --git a/conf/license/Artistic License 2.0 b/conf/license/Artistic License 2.0
new file mode 100644
index 000000000..b7c38097e
--- /dev/null
+++ b/conf/license/Artistic License 2.0
@@ -0,0 +1,201 @@
+The Artistic License 2.0
+
+ Copyright (c) 2014
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/gogs.go b/gogs.go
index 385b74217..41df79280 100644
--- a/gogs.go
+++ b/gogs.go
@@ -15,12 +15,12 @@ import (
"github.com/gogits/gogs/modules/base"
)
-// +build go1.1
+// +build go1.2
-// Test that go1.1 tag above is included in builds. main.go refers to this definition.
-const go11tag = true
+// Test that go1.2 tag above is included in builds. main.go refers to this definition.
+const go12tag = true
-const APP_VER = "0.1.1.0320.1"
+const APP_VER = "0.1.5.0321"
func init() {
base.AppVer = APP_VER
diff --git a/models/action.go b/models/action.go
index b3be09353..107d4b105 100644
--- a/models/action.go
+++ b/models/action.go
@@ -64,6 +64,10 @@ func CommitRepoAction(userId int64, userName string,
watches = append(watches, Watch{UserId: userId})
for i := range watches {
+ if userId == watches[i].UserId && i > 0 {
+ continue // Do not add twice in case author watches his/her repository.
+ }
+
_, err = orm.InsertOne(&Action{
UserId: watches[i].UserId,
ActUserId: userId,
diff --git a/models/issue.go b/models/issue.go
new file mode 100644
index 000000000..c669d201f
--- /dev/null
+++ b/models/issue.go
@@ -0,0 +1,19 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+type Issue struct {
+ Id int64
+ RepoId int64 `xorm:"index"`
+ PosterId int64
+}
+
+type PullRequest struct {
+ Id int64
+}
+
+type Comment struct {
+ Id int64
+}
diff --git a/models/models.go b/models/models.go
index 2e0bb759d..8713ff289 100644
--- a/models/models.go
+++ b/models/models.go
@@ -7,6 +7,7 @@ package models
import (
"fmt"
"os"
+ "path"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
@@ -16,48 +17,37 @@ import (
)
var (
- orm *xorm.Engine
- RepoRootPath string
+ orm *xorm.Engine
+
+ DbCfg struct {
+ Type, Host, Name, User, Pwd, Path, SslMode string
+ }
)
-type Members struct {
- Id int64
- OrgId int64 `xorm:"unique(s) index"`
- UserId int64 `xorm:"unique(s)"`
-}
-
-type Issue struct {
- Id int64
- RepoId int64 `xorm:"index"`
- PosterId int64
-}
-
-type PullRequest struct {
- Id int64
-}
-
-type Comment struct {
- Id int64
+func LoadModelsConfig() {
+ DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
+ DbCfg.Host = base.Cfg.MustValue("database", "HOST")
+ DbCfg.Name = base.Cfg.MustValue("database", "NAME")
+ DbCfg.User = base.Cfg.MustValue("database", "USER")
+ DbCfg.Pwd = base.Cfg.MustValue("database", "PASSWD")
+ DbCfg.SslMode = base.Cfg.MustValue("database", "SSL_MODE")
+ DbCfg.Path = base.Cfg.MustValue("database", "PATH", "data/gogs.db")
}
func setEngine() {
- dbType := base.Cfg.MustValue("database", "DB_TYPE")
- dbHost := base.Cfg.MustValue("database", "HOST")
- dbName := base.Cfg.MustValue("database", "NAME")
- dbUser := base.Cfg.MustValue("database", "USER")
- dbPwd := base.Cfg.MustValue("database", "PASSWD")
- sslMode := base.Cfg.MustValue("database", "SSL_MODE")
-
var err error
- switch dbType {
+ switch DbCfg.Type {
case "mysql":
orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@%s/%s?charset=utf8",
- dbUser, dbPwd, dbHost, dbName))
+ DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name))
case "postgres":
orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s",
- dbUser, dbPwd, dbName, sslMode))
+ DbCfg.User, DbCfg.Pwd, DbCfg.Name, DbCfg.SslMode))
+ case "sqlite3":
+ os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
+ orm, err = xorm.NewEngine("sqlite3", DbCfg.Path)
default:
- fmt.Printf("Unknown database type: %s\n", dbType)
+ fmt.Printf("Unknown database type: %s\n", DbCfg.Type)
os.Exit(2)
}
if err != nil {
@@ -65,8 +55,8 @@ func setEngine() {
os.Exit(2)
}
- // TODO: for serv command, MUST remove the output to os.stdout, so
- // use log file to instead print to stdout
+ // WARNNING: for serv command, MUST remove the output to os.stdout,
+ // so use log file to instead print to stdout.
//x.ShowDebug = true
//orm.ShowErr = true
@@ -77,20 +67,29 @@ func setEngine() {
}
orm.Logger = f
orm.ShowSQL = true
-
- // Determine and create root git reposiroty path.
- RepoRootPath = base.Cfg.MustValue("repository", "ROOT")
- if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
- fmt.Printf("models.init(fail to create RepoRootPath(%s)): %v\n", RepoRootPath, err)
- os.Exit(2)
- }
}
-func init() {
+func NewEngine() {
setEngine()
- if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Access),
- new(Action), new(Watch)); err != nil {
+ if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
+ new(Action), new(Access)); err != nil {
fmt.Printf("sync database struct error: %v\n", err)
os.Exit(2)
}
}
+
+type Statistic struct {
+ Counter struct {
+ User, PublicKey, Repo, Watch, Action, Access int64
+ }
+}
+
+func GetStatistic() (stats Statistic) {
+ stats.Counter.User, _ = orm.Count(new(User))
+ stats.Counter.PublicKey, _ = orm.Count(new(PublicKey))
+ stats.Counter.Repo, _ = orm.Count(new(Repository))
+ stats.Counter.Watch, _ = orm.Count(new(Watch))
+ stats.Counter.Action, _ = orm.Count(new(Action))
+ stats.Counter.Access, _ = orm.Count(new(Access))
+ return stats
+}
diff --git a/models/models_test.go b/models/models_test.go
index c44ef476c..d0f734d67 100644
--- a/models/models_test.go
+++ b/models/models_test.go
@@ -13,6 +13,9 @@ import (
)
func init() {
+ LoadModelsConfig()
+ NewEngine()
+
var err error
orm, err = xorm.NewEngine("sqlite3", "./test.db")
if err != nil {
diff --git a/models/publickey.go b/models/publickey.go
index 092436d55..c69bca681 100644
--- a/models/publickey.go
+++ b/models/publickey.go
@@ -27,8 +27,12 @@ const (
)
var (
- sshOpLocker = sync.Mutex{}
+ ErrKeyAlreadyExist = errors.New("Public key already exist")
+)
+var sshOpLocker = sync.Mutex{}
+
+var (
sshPath string
appPath string
)
@@ -79,10 +83,6 @@ type PublicKey struct {
Updated time.Time `xorm:"updated"`
}
-var (
- ErrKeyAlreadyExist = errors.New("Public key already exist")
-)
-
// GenAuthorizedKey returns formatted public key string.
func GenAuthorizedKey(keyId int64, key string) string {
return fmt.Sprintf(TPL_PUBLICK_KEY+"\n", appPath, keyId, key)
diff --git a/models/repo.go b/models/repo.go
index cf1e1df5c..4972661cb 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -12,6 +12,7 @@ import (
"os"
"path"
"path/filepath"
+ "regexp"
"strings"
"sync"
"time"
@@ -26,68 +27,26 @@ import (
"github.com/gogits/gogs/modules/log"
)
-// Repository represents a git repository.
-type Repository struct {
- Id int64
- OwnerId int64 `xorm:"unique(s)"`
- ForkId int64
- LowerName string `xorm:"unique(s) index not null"`
- Name string `xorm:"index not null"`
- Description string
- Website string
- Private bool
- NumWatchs int
- NumStars int
- NumForks int
- Created time.Time `xorm:"created"`
- Updated time.Time `xorm:"updated"`
-}
+var (
+ ErrRepoAlreadyExist = errors.New("Repository already exist")
+ ErrRepoNotExist = errors.New("Repository does not exist")
+ ErrRepoFileNotExist = errors.New("Target Repo file does not exist")
+ ErrRepoNameIllegal = errors.New("Repository name contains illegal characters")
+ ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded")
+)
-// Watch is connection request for receiving repository notifycation.
-type Watch struct {
- Id int64
- RepoId int64 `xorm:"UNIQUE(watch)"`
- UserId int64 `xorm:"UNIQUE(watch)"`
-}
-
-// Watch or unwatch repository.
-func WatchRepo(userId, repoId int64, watch bool) (err error) {
- if watch {
- _, err = orm.Insert(&Watch{RepoId: repoId, UserId: userId})
- } else {
- _, err = orm.Delete(&Watch{0, repoId, userId})
- }
- return err
-}
-
-// GetWatches returns all watches of given repository.
-func GetWatches(repoId int64) ([]Watch, error) {
- watches := make([]Watch, 0, 10)
- err := orm.Find(&watches, &Watch{RepoId: repoId})
- return watches, err
-}
-
-// IsWatching checks if user has watched given repository.
-func IsWatching(userId, repoId int64) bool {
- has, _ := orm.Get(&Watch{0, repoId, userId})
- return has
-}
+var gitInitLocker = sync.Mutex{}
var (
- gitInitLocker = sync.Mutex{}
LanguageIgns, Licenses []string
)
-var (
- ErrRepoAlreadyExist = errors.New("Repository already exist")
- ErrRepoNotExist = errors.New("Repository does not exist")
- ErrRepoFileNotExist = errors.New("Target Repo file does not exist")
-)
-
-func init() {
+func LoadRepoConfig() {
LanguageIgns = strings.Split(base.Cfg.MustValue("repository", "LANG_IGNS"), "|")
Licenses = strings.Split(base.Cfg.MustValue("repository", "LICENSES"), "|")
+}
+func NewRepoContext() {
zip.Verbose = false
// Check if server has basic git setting.
@@ -104,6 +63,32 @@ func init() {
os.Exit(2)
}
}
+
+ // Initialize illegal patterns.
+ for i := range illegalPatterns[1:] {
+ pattern := ""
+ for j := range illegalPatterns[i+1] {
+ pattern += "[" + string(illegalPatterns[i+1][j]-32) + string(illegalPatterns[i+1][j]) + "]"
+ }
+ illegalPatterns[i+1] = pattern
+ }
+}
+
+// Repository represents a git repository.
+type Repository struct {
+ Id int64
+ OwnerId int64 `xorm:"unique(s)"`
+ ForkId int64
+ LowerName string `xorm:"unique(s) index not null"`
+ Name string `xorm:"index not null"`
+ Description string
+ Website string
+ Private bool
+ NumWatches int
+ NumStars int
+ NumForks int
+ Created time.Time `xorm:"created"`
+ Updated time.Time `xorm:"updated"`
}
// IsRepositoryExist returns true if the repository with given name under user has already existed.
@@ -120,8 +105,28 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) {
return s.IsDir(), nil
}
+var (
+ // Define as all lower case!!
+ illegalPatterns = []string{"[.][Gg][Ii][Tt]", "user", "help", "stars", "issues", "pulls", "commits", "admin", "repo", "template", "admin"}
+)
+
+// IsLegalName returns false if name contains illegal characters.
+func IsLegalName(repoName string) bool {
+ for _, pattern := range illegalPatterns {
+ has, _ := regexp.MatchString(pattern, repoName)
+ if has {
+ return false
+ }
+ }
+ return true
+}
+
// CreateRepository creates a repository for given user or orgnaziation.
func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) {
+ if !IsLegalName(repoName) {
+ return nil, ErrRepoNameIllegal
+ }
+
isExist, err := IsRepositoryExist(user, repoName)
if err != nil {
return nil, err
@@ -331,6 +336,82 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
return nil
}
+// UserRepo reporesents a repository with user name.
+type UserRepo struct {
+ *Repository
+ UserName string
+}
+
+// GetRepos returns given number of repository objects with offset.
+func GetRepos(num, offset int) ([]UserRepo, error) {
+ repos := make([]Repository, 0, num)
+ if err := orm.Limit(num, offset).Asc("id").Find(&repos); err != nil {
+ return nil, err
+ }
+
+ urepos := make([]UserRepo, len(repos))
+ for i := range repos {
+ urepos[i].Repository = &repos[i]
+ u := new(User)
+ has, err := orm.Id(urepos[i].Repository.OwnerId).Get(u)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrUserNotExist
+ }
+ urepos[i].UserName = u.Name
+ }
+
+ return urepos, nil
+}
+
+func RepoPath(userName, repoName string) string {
+ return filepath.Join(UserPath(userName), repoName+".git")
+}
+
+// DeleteRepository deletes a repository for a user or orgnaztion.
+func DeleteRepository(userId, repoId int64, userName string) (err error) {
+ repo := &Repository{Id: repoId, OwnerId: userId}
+ has, err := orm.Get(repo)
+ if err != nil {
+ return err
+ } else if !has {
+ return ErrRepoNotExist
+ }
+
+ session := orm.NewSession()
+ if err = session.Begin(); err != nil {
+ return err
+ }
+ if _, err = session.Delete(&Repository{Id: repoId}); err != nil {
+ session.Rollback()
+ return err
+ }
+ if _, err := session.Delete(&Access{UserName: userName, RepoName: repo.Name}); err != nil {
+ session.Rollback()
+ return err
+ }
+ rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
+ if _, err = session.Exec(rawSql, userId); err != nil {
+ session.Rollback()
+ return err
+ }
+ if _, err = session.Delete(&Watch{RepoId: repoId}); err != nil {
+ session.Rollback()
+ return err
+ }
+ if err = session.Commit(); err != nil {
+ session.Rollback()
+ return err
+ }
+ if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
+ // TODO: log and delete manully
+ log.Error("delete repo %s/%s failed: %v", userName, repo.Name, err)
+ return err
+ }
+ return nil
+}
+
// GetRepositoryByName returns the repository by given name under user if exists.
func GetRepositoryByName(user *User, repoName string) (*Repository, error) {
repo := &Repository{
@@ -368,6 +449,45 @@ func GetRepositoryCount(user *User) (int64, error) {
return orm.Count(&Repository{OwnerId: user.Id})
}
+// Watch is connection request for receiving repository notifycation.
+type Watch struct {
+ Id int64
+ RepoId int64 `xorm:"UNIQUE(watch)"`
+ UserId int64 `xorm:"UNIQUE(watch)"`
+}
+
+// Watch or unwatch repository.
+func WatchRepo(userId, repoId int64, watch bool) (err error) {
+ if watch {
+ if _, err = orm.Insert(&Watch{RepoId: repoId, UserId: userId}); err != nil {
+ return err
+ }
+
+ rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
+ _, err = orm.Exec(rawSql, repoId)
+ } else {
+ if _, err = orm.Delete(&Watch{0, repoId, userId}); err != nil {
+ return err
+ }
+ rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?"
+ _, err = orm.Exec(rawSql, repoId)
+ }
+ return err
+}
+
+// GetWatches returns all watches of given repository.
+func GetWatches(repoId int64) ([]Watch, error) {
+ watches := make([]Watch, 0, 10)
+ err := orm.Find(&watches, &Watch{RepoId: repoId})
+ return watches, err
+}
+
+// IsWatching checks if user has watched given repository.
+func IsWatching(userId, repoId int64) bool {
+ has, _ := orm.Get(&Watch{0, repoId, userId})
+ return has
+}
+
func StarReposiory(user *User, repoName string) error {
return nil
}
@@ -388,56 +508,6 @@ func ForkRepository(reposName string, userId int64) {
}
-func RepoPath(userName, repoName string) string {
- return filepath.Join(UserPath(userName), repoName+".git")
-}
-
-// DeleteRepository deletes a repository for a user or orgnaztion.
-func DeleteRepository(userId, repoId int64, userName string) (err error) {
- repo := &Repository{Id: repoId, OwnerId: userId}
- has, err := orm.Get(repo)
- if err != nil {
- return err
- } else if !has {
- return ErrRepoNotExist
- }
-
- session := orm.NewSession()
- if err = session.Begin(); err != nil {
- return err
- }
- if _, err = session.Delete(&Repository{Id: repoId}); err != nil {
- session.Rollback()
- return err
- }
- if _, err := session.Delete(&Access{UserName: userName, RepoName: repo.Name}); err != nil {
- session.Rollback()
- return err
- }
- rawSql := "UPDATE user SET num_repos = num_repos - 1 WHERE id = ?"
- if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" {
- rawSql = "UPDATE \"user\" SET num_repos = num_repos - 1 WHERE id = ?"
- }
- if _, err = session.Exec(rawSql, userId); err != nil {
- session.Rollback()
- return err
- }
- if err = session.Commit(); err != nil {
- session.Rollback()
- return err
- }
- if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
- // TODO: log and delete manully
- log.Error("delete repo %s/%s failed: %v", userName, repo.Name, err)
- return err
- }
- return nil
-}
-
-var (
- ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded")
-)
-
// RepoFile represents a file object in git repository.
type RepoFile struct {
*git.TreeEntry
diff --git a/models/user.go b/models/user.go
index 69608ec27..3c1109128 100644
--- a/models/user.go
+++ b/models/user.go
@@ -33,6 +33,14 @@ const (
LT_LDAP
)
+var (
+ ErrUserOwnRepos = errors.New("User still have ownership of repositories")
+ ErrUserAlreadyExist = errors.New("User already exist")
+ ErrUserNotExist = errors.New("User does not exist")
+ ErrEmailAlreadyUsed = errors.New("E-mail already used")
+ ErrUserNameIllegal = errors.New("User name contains illegal characters")
+)
+
// User represents the object of individual and member of organization.
type User struct {
Id int64
@@ -51,6 +59,7 @@ type User struct {
Location string
Website string
IsActive bool
+ IsAdmin bool
Rands string `xorm:"VARCHAR(10)"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
@@ -66,19 +75,28 @@ func (user *User) AvatarLink() string {
return "http://1.gravatar.com/avatar/" + user.Avatar
}
-type Follow struct {
- Id int64
- UserId int64 `xorm:"unique(s)"`
- FollowId int64 `xorm:"unique(s)"`
- Created time.Time `xorm:"created"`
+// NewGitSig generates and returns the signature of given user.
+func (user *User) NewGitSig() *git.Signature {
+ return &git.Signature{
+ Name: user.Name,
+ Email: user.Email,
+ When: time.Now(),
+ }
}
-var (
- ErrUserOwnRepos = errors.New("User still have ownership of repositories")
- ErrUserAlreadyExist = errors.New("User already exist")
- ErrUserNotExist = errors.New("User does not exist")
- ErrEmailAlreadyUsed = errors.New("E-mail already used")
-)
+// EncodePasswd encodes password to safe format.
+func (user *User) EncodePasswd() error {
+ newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64)
+ user.Passwd = fmt.Sprintf("%x", newPasswd)
+ return err
+}
+
+// Member represents user is member of organization.
+type Member struct {
+ Id int64
+ OrgId int64 `xorm:"unique(member) index"`
+ UserId int64 `xorm:"unique(member)"`
+}
// IsUserExist checks if given user name exist,
// the user name should be noncased unique.
@@ -91,15 +109,6 @@ func IsEmailUsed(email string) (bool, error) {
return orm.Get(&User{Email: email})
}
-// NewGitSig generates and returns the signature of given user.
-func (user *User) NewGitSig() *git.Signature {
- return &git.Signature{
- Name: user.Name,
- Email: user.Email,
- When: time.Now(),
- }
-}
-
// return a user salt token
func GetUserSalt() string {
return base.GetRandomString(10)
@@ -107,6 +116,10 @@ func GetUserSalt() string {
// RegisterUser creates record of a new user.
func RegisterUser(user *User) (*User, error) {
+ if !IsLegalName(user.Name) {
+ return nil, ErrUserNameIllegal
+ }
+
isExist, err := IsUserExist(user.Name)
if err != nil {
return nil, err
@@ -136,7 +149,20 @@ func RegisterUser(user *User) (*User, error) {
}
return nil, err
}
- return user, nil
+
+ if user.Id == 1 {
+ user.IsAdmin = true
+ user.IsActive = true
+ _, err = orm.Id(user.Id).UseBool().Update(user)
+ }
+ return user, err
+}
+
+// GetUsers returns given number of user objects with offset.
+func GetUsers(num, offset int) ([]User, error) {
+ users := make([]User, 0, num)
+ err := orm.Limit(num, offset).Asc("id").Find(&users)
+ return users, err
}
// get user by erify code
@@ -217,22 +243,14 @@ func DeleteUser(user *User) error {
return err
}
-// EncodePasswd encodes password to safe format.
-func (user *User) EncodePasswd() error {
- newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64)
- user.Passwd = fmt.Sprintf("%x", newPasswd)
- return err
-}
-
// UserPath returns the path absolute path of user repositories.
func UserPath(userName string) string {
- return filepath.Join(RepoRootPath, strings.ToLower(userName))
+ return filepath.Join(base.RepoRootPath, strings.ToLower(userName))
}
func GetUserByKeyId(keyId int64) (*User, error) {
user := new(User)
rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?"
-
has, err := orm.Sql(rawSql, keyId).Get(user)
if err != nil {
return nil, err
@@ -289,6 +307,13 @@ func LoginUserPlain(name, passwd string) (*User, error) {
return &user, err
}
+// Follow is connection request for receiving user notifycation.
+type Follow struct {
+ Id int64
+ UserId int64 `xorm:"unique(follow)"`
+ FollowId int64 `xorm:"unique(follow)"`
+}
+
// FollowUser marks someone be another's follower.
func FollowUser(userId int64, followId int64) (err error) {
session := orm.NewSession()
@@ -300,19 +325,13 @@ func FollowUser(userId int64, followId int64) (err error) {
return err
}
- rawSql := "UPDATE user SET num_followers = num_followers + 1 WHERE id = ?"
- if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" {
- rawSql = "UPDATE \"user\" SET num_followers = num_followers + 1 WHERE id = ?"
- }
+ rawSql := "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?"
if _, err = session.Exec(rawSql, followId); err != nil {
session.Rollback()
return err
}
- rawSql = "UPDATE user SET num_followings = num_followings + 1 WHERE id = ?"
- if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" {
- rawSql = "UPDATE \"user\" SET num_followings = num_followings + 1 WHERE id = ?"
- }
+ rawSql = "UPDATE `user` SET num_followings = num_followings + 1 WHERE id = ?"
if _, err = session.Exec(rawSql, userId); err != nil {
session.Rollback()
return err
@@ -331,19 +350,13 @@ func UnFollowUser(userId int64, unFollowId int64) (err error) {
return err
}
- rawSql := "UPDATE user SET num_followers = num_followers - 1 WHERE id = ?"
- if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" {
- rawSql = "UPDATE \"user\" SET num_followers = num_followers - 1 WHERE id = ?"
- }
+ rawSql := "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?"
if _, err = session.Exec(rawSql, unFollowId); err != nil {
session.Rollback()
return err
}
- rawSql = "UPDATE user SET num_followings = num_followings - 1 WHERE id = ?"
- if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" {
- rawSql = "UPDATE \"user\" SET num_followings = num_followings - 1 WHERE id = ?"
- }
+ rawSql = "UPDATE `user` SET num_followings = num_followings - 1 WHERE id = ?"
if _, err = session.Exec(rawSql, userId); err != nil {
session.Rollback()
return err
diff --git a/modules/auth/admin.go b/modules/auth/admin.go
new file mode 100644
index 000000000..eccab0071
--- /dev/null
+++ b/modules/auth/admin.go
@@ -0,0 +1,55 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+ "net/http"
+ "reflect"
+
+ "github.com/codegangsta/martini"
+
+ "github.com/gogits/binding"
+
+ "github.com/gogits/gogs/modules/base"
+ "github.com/gogits/gogs/modules/log"
+)
+
+type AdminEditUserForm struct {
+ Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
+ Website string `form:"website" binding:"MaxSize(50)"`
+ Location string `form:"location" binding:"MaxSize(50)"`
+ Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
+ Active string `form:"active"`
+ Admin string `form:"admin"`
+}
+
+func (f *AdminEditUserForm) Name(field string) string {
+ names := map[string]string{
+ "Email": "E-mail address",
+ "Website": "Website",
+ "Location": "Location",
+ "Avatar": "Gravatar Email",
+ }
+ return names[field]
+}
+
+func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+ if req.Method == "GET" || errors.Count() == 0 {
+ return
+ }
+
+ data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ data["HasError"] = true
+ AssignForm(f, data)
+
+ if len(errors.Overall) > 0 {
+ for _, err := range errors.Overall {
+ log.Error("AdminEditUserForm.Validate: %v", err)
+ }
+ return
+ }
+
+ validate(errors, data, f)
+}
diff --git a/modules/auth/user.go b/modules/auth/user.go
index 491ec65ac..f8d8f6614 100644
--- a/modules/auth/user.go
+++ b/modules/auth/user.go
@@ -79,7 +79,7 @@ type UpdateProfileForm struct {
func (f *UpdateProfileForm) Name(field string) string {
names := map[string]string{
- "Email": "Email address",
+ "Email": "E-mail address",
"Website": "Website",
"Location": "Location",
"Avatar": "Gravatar Email",
diff --git a/modules/base/conf.go b/modules/base/conf.go
index fdbf3ad38..863daca64 100644
--- a/modules/base/conf.go
+++ b/modules/base/conf.go
@@ -15,6 +15,8 @@ import (
"github.com/Unknwon/com"
"github.com/Unknwon/goconfig"
+ "github.com/gogits/cache"
+
"github.com/gogits/gogs/modules/log"
)
@@ -26,20 +28,32 @@ type Mailer struct {
}
var (
- AppVer string
- AppName string
- AppLogo string
- AppUrl string
- Domain string
- SecretKey string
+ AppVer string
+ AppName string
+ AppLogo string
+ AppUrl string
+ Domain string
+ SecretKey string
+ RunUser string
+ RepoRootPath string
+
Cfg *goconfig.ConfigFile
MailService *Mailer
+
+ Cache cache.Cache
+ CacheAdapter string
+ CacheConfig string
+
+ LogMode string
+ LogConfig string
)
var Service struct {
- RegisterEmailConfirm bool
- ActiveCodeLives int
- ResetPwdCodeLives int
+ RegisterEmailConfirm bool
+ DisenableRegisteration bool
+ RequireSignInView bool
+ ActiveCodeLives int
+ ResetPwdCodeLives int
}
func exeDir() (string, error) {
@@ -66,19 +80,21 @@ var logLevels = map[string]string{
func newService() {
Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180)
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
+ Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false)
+ Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false)
}
func newLogService() {
// Get and check log mode.
- mode := Cfg.MustValue("log", "MODE", "console")
- modeSec := "log." + mode
+ LogMode = Cfg.MustValue("log", "MODE", "console")
+ modeSec := "log." + LogMode
if _, err := Cfg.GetSection(modeSec); err != nil {
- fmt.Printf("Unknown log mode: %s\n", mode)
+ fmt.Printf("Unknown log mode: %s\n", LogMode)
os.Exit(2)
}
// Log level.
- levelName := Cfg.MustValue("log."+mode, "LEVEL", "Trace")
+ levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
level, ok := logLevels[levelName]
if !ok {
fmt.Printf("Unknown log level: %s\n", levelName)
@@ -86,14 +102,13 @@ func newLogService() {
}
// Generate log configuration.
- var config string
- switch mode {
+ switch LogMode {
case "console":
- config = fmt.Sprintf(`{"level":%s}`, level)
+ LogConfig = fmt.Sprintf(`{"level":%s}`, level)
case "file":
logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log")
os.MkdirAll(path.Dir(logPath), os.ModePerm)
- config = fmt.Sprintf(
+ LogConfig = fmt.Sprintf(
`{"level":%s,"filename":%s,"rotate":%v,"maxlines":%d,"maxsize",%d,"daily":%v,"maxdays":%d}`, level,
logPath,
Cfg.MustBool(modeSec, "LOG_ROTATE", true),
@@ -102,13 +117,13 @@ func newLogService() {
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
case "conn":
- config = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":%s,"addr":%s}`, level,
+ LogConfig = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":%s,"addr":%s}`, level,
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false),
Cfg.MustBool(modeSec, "RECONNECT", false),
Cfg.MustValue(modeSec, "PROTOCOL", "tcp"),
Cfg.MustValue(modeSec, "ADDR", ":7020"))
case "smtp":
- config = fmt.Sprintf(`{"level":%s,"username":%s,"password":%s,"host":%s,"sendTos":%s,"subject":%s}`, level,
+ LogConfig = fmt.Sprintf(`{"level":%s,"username":%s,"password":%s,"host":%s,"sendTos":%s,"subject":%s}`, level,
Cfg.MustValue(modeSec, "USER", "example@example.com"),
Cfg.MustValue(modeSec, "PASSWD", "******"),
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
@@ -116,8 +131,32 @@ func newLogService() {
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
}
- log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), mode, config)
- log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName)
+ log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
+ log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
+}
+
+func newCacheService() {
+ CacheAdapter = Cfg.MustValue("cache", "ADAPTER", "memory")
+
+ switch CacheAdapter {
+ case "memory":
+ CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60))
+ case "redis", "memcache":
+ CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
+ default:
+ fmt.Printf("Unknown cache adapter: %s\n", CacheAdapter)
+ os.Exit(2)
+ }
+
+ var err error
+ Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
+ if err != nil {
+ fmt.Printf("Init cache system failed, adapter: %s, config: %s, %v\n",
+ CacheAdapter, CacheConfig, err)
+ os.Exit(2)
+ }
+
+ log.Info("Cache Service Enabled")
}
func newMailService() {
@@ -144,7 +183,7 @@ func newRegisterMailService() {
log.Info("Register Mail Service Enabled")
}
-func init() {
+func NewConfigContext() {
var err error
workDir, err := exeDir()
if err != nil {
@@ -173,11 +212,20 @@ func init() {
AppUrl = Cfg.MustValue("server", "ROOT_URL")
Domain = Cfg.MustValue("server", "DOMAIN")
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
+ RunUser = Cfg.MustValue("", "RUN_USER")
+
+ // Determine and create root git reposiroty path.
+ RepoRootPath = Cfg.MustValue("repository", "ROOT")
+ if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
+ fmt.Printf("models.init(fail to create RepoRootPath(%s)): %v\n", RepoRootPath, err)
+ os.Exit(2)
+ }
}
func NewServices() {
newService()
newLogService()
+ newCacheService()
newMailService()
newRegisterMailService()
}
diff --git a/modules/base/markdown.go b/modules/base/markdown.go
index a9f4cbf0d..2273cd772 100644
--- a/modules/base/markdown.go
+++ b/modules/base/markdown.go
@@ -36,7 +36,7 @@ func isLink(link []byte) bool {
func IsMarkdownFile(name string) bool {
name = strings.ToLower(name)
switch filepath.Ext(name) {
- case "md", "markdown":
+ case ".md", ".markdown", ".mdown":
return true
}
return false
@@ -61,7 +61,7 @@ type CustomRender struct {
func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
if len(link) > 0 && !isLink(link) {
if link[0] == '#' {
- link = append([]byte(options.urlPrefix), link...)
+ // link = append([]byte(options.urlPrefix), link...)
} else {
link = []byte(path.Join(options.urlPrefix, string(link)))
}
diff --git a/modules/base/template.go b/modules/base/template.go
index e596d1dad..8d95dbeab 100644
--- a/modules/base/template.go
+++ b/modules/base/template.go
@@ -33,6 +33,10 @@ func List(l *list.List) chan interface{} {
return c
}
+var mailDomains = map[string]string{
+ "gmail.com": "gmail.com",
+}
+
var TemplateFuncs template.FuncMap = map[string]interface{}{
"AppName": func() string {
return AppName
@@ -56,7 +60,12 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DateFormat": DateFormat,
"List": List,
"Mail2Domain": func(mail string) string {
- return "mail." + strings.Split(mail, "@")[1]
+ suffix := strings.SplitN(mail, "@", 2)[1]
+ domain, ok := mailDomains[suffix]
+ if !ok {
+ return "mail." + suffix
+ }
+ return domain
},
"SubStr": func(str string, start, length int) string {
return str[start : start+length]
diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go
index 150607f8c..da63e01d2 100644
--- a/modules/mailer/mailer.go
+++ b/modules/mailer/mailer.go
@@ -40,7 +40,7 @@ func (m Message) Content() string {
var mailQueue chan *Message
-func init() {
+func NewMailerContext() {
mailQueue = make(chan *Message, base.Cfg.MustInt("mailer", "SEND_BUFFER_LEN", 10))
go processMailQueue()
}
diff --git a/modules/middleware/auth.go b/modules/middleware/auth.go
index d45a21e98..f211de32b 100644
--- a/modules/middleware/auth.go
+++ b/modules/middleware/auth.go
@@ -15,12 +15,12 @@ func SignInRequire(redirect bool) martini.Handler {
return func(ctx *Context) {
if !ctx.IsSigned {
if redirect {
- ctx.Redirect("/")
+ ctx.Redirect("/user/login")
}
return
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
ctx.Data["Title"] = "Activate Your Account"
- ctx.Render.HTML(200, "user/active", ctx.Data)
+ ctx.HTML(200, "user/active")
return
}
}
@@ -31,6 +31,18 @@ func SignOutRequire() martini.Handler {
return func(ctx *Context) {
if ctx.IsSigned {
ctx.Redirect("/")
+ return
}
}
}
+
+// AdminRequire requires user signed in as administor.
+func AdminRequire() martini.Handler {
+ return func(ctx *Context) {
+ if !ctx.User.IsAdmin {
+ ctx.Error(403)
+ return
+ }
+ ctx.Data["PageIsAdmin"] = true
+ }
+}
diff --git a/modules/middleware/context.go b/modules/middleware/context.go
index 6ac87de3b..a25a3dbbe 100644
--- a/modules/middleware/context.go
+++ b/modules/middleware/context.go
@@ -12,8 +12,11 @@ import (
"github.com/codegangsta/martini"
"github.com/martini-contrib/sessions"
+ "github.com/gogits/cache"
+
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
+ "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
@@ -25,6 +28,7 @@ type Context struct {
Req *http.Request
Res http.ResponseWriter
Session sessions.Session
+ Cache cache.Cache
User *models.User
IsSigned bool
@@ -61,24 +65,29 @@ func (ctx *Context) HasError() bool {
return hasErr.(bool)
}
+// HTML calls render.HTML underlying but reduce one argument.
+func (ctx *Context) HTML(status int, name string, htmlOpt ...HTMLOptions) {
+ ctx.Render.HTML(status, name, ctx.Data, htmlOpt...)
+}
+
// RenderWithErr used for page has form validation but need to prompt error to users.
func (ctx *Context) RenderWithErr(msg, tpl string, form auth.Form) {
ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = msg
auth.AssignForm(form, ctx.Data)
- ctx.HTML(200, tpl, ctx.Data)
+ ctx.HTML(200, tpl)
}
// Handle handles and logs error by given status.
func (ctx *Context) Handle(status int, title string, err error) {
log.Error("%s: %v", title, err)
if martini.Dev == martini.Prod {
- ctx.HTML(500, "status/500", ctx.Data)
+ ctx.HTML(500, "status/500")
return
}
ctx.Data["ErrorMsg"] = err
- ctx.HTML(status, fmt.Sprintf("status/%d", status), ctx.Data)
+ ctx.HTML(status, fmt.Sprintf("status/%d", status))
}
// InitContext initializes a classic context for a request.
@@ -92,6 +101,7 @@ func InitContext() martini.Handler {
Req: r,
Res: res,
Session: session,
+ Cache: base.Cache,
Render: rd,
}
@@ -106,6 +116,7 @@ func InitContext() martini.Handler {
ctx.Data["SignedUser"] = user
ctx.Data["SignedUserId"] = user.Id
ctx.Data["SignedUserName"] = user.LowerName
+ ctx.Data["IsAdmin"] = ctx.User.IsAdmin
}
ctx.Data["PageStartTime"] = time.Now()
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index b25c94231..a9a90e3ff 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -70,6 +70,7 @@ func RepoAssignment(redirect bool) martini.Handler {
}
ctx.Repo.Repository = repo
ctx.Repo.CloneLink.SSH = fmt.Sprintf("git@%s:%s/%s.git", base.Domain, user.LowerName, repo.LowerName)
+ ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("https://%s/%s/%s.git", base.Domain, user.LowerName, repo.LowerName)
ctx.Data["IsRepositoryValid"] = true
ctx.Data["Repository"] = repo
@@ -78,5 +79,6 @@ func RepoAssignment(redirect bool) martini.Handler {
ctx.Data["CloneLink"] = ctx.Repo.CloneLink
ctx.Data["RepositoryLink"] = ctx.Data["Title"]
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
+ ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching
}
}
diff --git a/public/css/gogs.css b/public/css/gogs.css
index a4767c1c8..78040bee5 100755
--- a/public/css/gogs.css
+++ b/public/css/gogs.css
@@ -145,8 +145,9 @@ html, body {
float: right;
}
-#gogs-nav-signin {
+#gogs-nav-signin, #gogs-nav-signup {
float: right;
+ margin-left: 1em;
}
#gogs-nav-out .fa {
@@ -324,6 +325,27 @@ html, body {
color: #444;
}
+.gogs-admin-nav {
+ background-color: #FFF;
+ padding-top: 10px;
+ padding-left: 0;
+ padding-right: 0;
+ border: 1px solid #D8D8D8;
+}
+
+.gogs-admin-nav li {
+ margin-bottom: 8px;
+ border-left: 4px solid transparent;
+}
+
+.gogs-admin-nav li:hover {
+ border-left-color: #EEE;
+}
+
+.gogs-admin-nav li.active:hover {
+ border-left: 4px solid #DD4B39;
+}
+
/* gogits user ssh keys */
#gogs-ssh-keys .list-group-item {
@@ -376,7 +398,7 @@ html, body {
}
#gogs-feed-right .repo-panel .list-group-item a {
- display: inline-block;
+ display: block;
margin-left: 0;
background-color: transparent;
padding-left: 0;
@@ -406,7 +428,6 @@ html, body {
#gogs-feed-right .repo-panel span.stars {
color: #666;
- line-height: 44px;
margin-right: 1em;
}
@@ -445,7 +466,7 @@ html, body {
padding: 0;
}
-#gogs-repo-watching .dropdown-menu .dropdown-item:hover .dropdown-header {
+#gogs-repo-watching .dropdown-menu .dropdown-item:hover .dropdown-header, #gogs-repo-watching .dropdown-item .dropdown-header.text-primary {
color: rgb(65, 131, 196);
cursor: pointer;
}
@@ -501,8 +522,7 @@ html, body {
}
.activity-list .info {
- float: left;
- padding: 0 0 0 10px;
+ margin: 0 0 0 40px;
line-height: 1.7em;
}
@@ -555,6 +575,15 @@ html, body {
min-width: 200px;
}
+#gogs-repo-clone .dropdown-menu{
+ width: 400px;
+ padding: 20px;
+}
+
+#gogs-repo-clone .input-group{
+ margin-bottom: 15px;
+}
+
/* #gogs-source */
#gogs-source {
margin-top: -20px;
@@ -645,6 +674,62 @@ html, body {
.file-content .file-body {
padding: 30px 30px 50px;
+ border: none;
+ background-color: #FFF;
+ overflow: auto;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+.file-content .file-body.file-code pre {
+ background-color: #FFF;
+ border: none;
+}
+
+.file-content .file-body.file-code {
+ padding: 0;
+}
+
+.file-content .file-body.file-code .lines-code > pre {
+ border: none;
+ background: none;
+ border-left: 1px solid #ddd;
+}
+
+.file-content .file-body.file-code .lines-code ol.linenums > .active {
+ background: #ffffdd;
+}
+
+.file-content .file-body.file-code .lines-num {
+ text-align: right;
+ color: #999;
+ background: #fafafa;
+ width: 1%;
+}
+
+.file-content .file-body.file-code .lines-num span {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+ line-height: 1.6;
+ padding: 0 8px 0 10px;
+ cursor: pointer;
+ display: block;
+ margin-top: 2px;
+ font-size: 90%;
+}
+
+.file-content .file-body.file-code .lines-num span:first-child {
+ margin-top: 0;
+}
+
+.file-content .file-body.file-code > table {
+ width: 100%;
+}
+
+.file-content .file-body.file-code > table > tbody > tr,
+.file-content .file-body.file-code > table > tbody > tr > td,
+.file-content .file-body.file-code > table {
+ border: none;
+ background: none;
}
.branch-list th, .commit-list th {
@@ -724,6 +809,28 @@ html, body {
background-color: #FFF;
}
+.commit-list .date {
+ width: 120px;
+}
+
+.commit-list .author {
+ min-width: 180px;
+}
+
+.guide-box pre, .guide-box .input-group {
+ margin-top: 20px;
+ margin-bottom: 30px;
+ line-height: 24px;
+}
+
+.guide-box input[readonly] {
+ background-color: #FFF;
+}
+
+.guide-box {
+ margin-top: 20px;
+}
+
/* wrapper and footer */
#wrapper {
diff --git a/public/css/markdown.css b/public/css/markdown.css
index 9283fb847..a810fa3ce 100644
--- a/public/css/markdown.css
+++ b/public/css/markdown.css
@@ -15,7 +15,8 @@
line-height: 1.7;
padding: 15px 0 0;
margin: 0 0 15px;
- color: #666;
+ color: #444;
+ font-weight: bold;
}
.markdown h1,
@@ -86,6 +87,10 @@
margin-top: 6px;
}
+.markdown li:first-child {
+ margin-top: 0;
+}
+
.markdown dl dt {
font-style: italic;
margin-top: 9px;
@@ -106,8 +111,8 @@
line-height: 1.6;
overflow: auto;
background: #f8f8f8;
- padding: 6px 10px;
border: 1px solid #ddd;
+ padding: 0;
}
.markdown > pre.linenums {
@@ -115,6 +120,17 @@
}
.markdown > pre > ol.linenums {
+ list-style: none;
+ padding: 0;
+}
+
+.markdown > pre > ol.linenums > li {
+ margin-top: 2px;
+}
+
+.markdown > pre.nums-style > ol.linenums {
+ list-style-type: decimal;
+ padding: 0 0 0 40px;
-webkit-box-shadow: inset 40px 0 0 #f5f5f5, inset 41px 0 0 #ccc;
box-shadow: inset 40px 0 0 #f5f5f5, inset 41px 0 0 #ccc;
}
@@ -130,14 +146,14 @@
}
.markdown > pre > ol.linenums > li:first-child {
- padding-top: 6px;
+ padding-top: 12px;
}
.markdown > pre > ol.linenums > li:last-child {
- padding-bottom: 6px;
+ padding-bottom: 12px;
}
-.markdown > pre > ol.linenums > li {
+.markdown > pre.nums-style > ol.linenums > li {
border-left: 1px solid #ddd;
}
@@ -163,6 +179,54 @@
color: #fff;
}
+.markdown .anchor-wrap {
+ /*margin-top: -50px;*/
+ /*padding-top: 50px;*/
+}
+
+.markdown h1 a, .markdown h2 a, .markdown h3 a {
+ text-decoration: none;
+}
+
+.markdown h1 a.anchor,
+.markdown h2 a.anchor,
+.markdown h3 a.anchor,
+.markdown h4 a.anchor,
+.markdown h5 a.anchor,
+.markdown h6 a.anchor {
+ text-decoration:none;
+ line-height:1;
+ padding-left:0;
+ margin-left:5px;
+ top:15%;
+}
+.markdown a span.octicon {
+ font-size: 16px;
+ font-family: "FontAwesome";
+ line-height: 1;
+ display: inline-block;
+ text-decoration: none;
+ -webkit-font-smoothing: antialiased;
+}
+
+.markdown a span.octicon-link {
+ display: none;
+ color: #000;
+}
+
+.markdown a span.octicon-link:before {
+ content: "\f0c1";
+}
+
+.markdown h1:hover .octicon-link,
+.markdown h2:hover .octicon-link,
+.markdown h3:hover .octicon-link,
+.markdown h4:hover .octicon-link,
+.markdown h5:hover .octicon-link,
+.markdown h6:hover .octicon-link {
+ display:inline-block
+}
+
/* Author: jmblog */
/* Project: https://github.com/jmblog/color-themes-for-google-code-prettify */
/* GitHub Theme */
@@ -187,6 +251,7 @@
/* a comment */
.com {
color: #999988;
+ font-style: italic;
}
/* a type name */
diff --git a/public/js/app.js b/public/js/app.js
index 30296bc33..f179342f4 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -41,15 +41,15 @@ var Gogits = {
});
};
Gogits.initPopovers = function () {
- var hideAllPopovers = function() {
- $('[data-toggle=popover]').each(function() {
+ var hideAllPopovers = function () {
+ $('[data-toggle=popover]').each(function () {
$(this).popover('hide');
- });
+ });
};
- $(document).on('click', function(e) {
+ $(document).on('click', function (e) {
var $e = $(e.target);
- if($e.data('toggle') == 'popover'||$e.parents("[data-toggle=popover], .popover").length > 0){
+ if ($e.data('toggle') == 'popover' || $e.parents("[data-toggle=popover], .popover").length > 0) {
return;
}
hideAllPopovers();
@@ -63,12 +63,56 @@ var Gogits = {
var $tabs = $('[data-init=tabs]');
$tabs.find("li:eq(0) a").tab("show");
};
+ // fix dropdown inside click
+ Gogits.initDropDown = function(){
+ $('.dropdown-menu').on('click','a,button,input,select',function(e){
+ e.stopPropagation();
+ });
+ };
// render markdown
Gogits.renderMarkdown = function () {
- var $pre = $('.markdown').find('pre > code').parent();
- $pre.addClass("prettyprint");
+ var $md = $('.markdown');
+ var $pre = $md.find('pre > code').parent();
+ $pre.addClass('prettyprint linenums');
prettyPrint();
+
+ var $lineNums = $pre.parent().siblings('.lines-num');
+ if ($lineNums.length > 0) {
+ var nums = $pre.find('ol.linenums > li').length;
+ for (var i = 1; i <= nums; i++) {
+ $lineNums.append('' + i + '');
+ }
+
+ var last;
+ $(document).on('click', '.lines-num span', function () {
+ var $e = $(this);
+ if (last) {
+ last.removeClass('active');
+ }
+ last = $e.parent().siblings('.lines-code').find('ol.linenums > ' + $e.attr('rel'));
+ last.addClass('active');
+ window.location.href = '#' + $e.attr('id');
+ });
+ }
+
+ // Set anchor.
+ var headers = {};
+ $md.find('h1, h2, h3, h4, h5, h6').each(function () {
+ var node = $(this);
+ var val = encodeURIComponent(node.text().toLowerCase().replace(/[^\w\- ]/g, '').replace(/[ ]/g, '-'));
+ var name = val;
+ if (headers[val] > 0) {
+ name = val + '-' + headers[val];
+ }
+ if (headers[val] == undefined) {
+ headers[val] = 1;
+ } else {
+ headers[val] += 1;
+ }
+ node = node.wrap('
');
+ node.append('');
+ });
}
})(jQuery);
@@ -98,6 +142,7 @@ function initCore() {
Gogits.initPopovers();
Gogits.initTabs();
Gogits.initModals();
+ Gogits.initDropDown();
Gogits.renderMarkdown();
}
@@ -142,6 +187,60 @@ function initUserSetting() {
});
}
+function initRepository() {
+ // clone group button script
+ (function () {
+ var $clone = $('.clone-group-btn');
+ if ($clone.length) {
+ var $url = $('.clone-group-url');
+ $clone.find('button[data-link]').on("click",function (e) {
+ var $this = $(this);
+ if (!$this.hasClass('btn-primary')) {
+ $clone.find('.btn-primary').removeClass('btn-primary').addClass("btn-default");
+ $(this).addClass('btn-primary').removeClass('btn-default');
+ $url.val($this.data("link"));
+ $clone.find('span.clone-url').text($this.data('link'));
+ }
+ }).eq(0).trigger("click");
+ // todo copy to clipboard
+ }
+ })();
+
+ // watching script
+ (function () {
+ var $watch = $('#gogs-repo-watching'),
+ watchLink = $watch.data("watch"),
+ unwatchLink = $watch.data("unwatch");
+ $watch.on('click', '.to-watch',function () {
+ if ($watch.hasClass("watching")) {
+ return false;
+ }
+ $.get(watchLink, function (json) {
+ if (json.ok) {
+ $watch.find('.text-primary').removeClass('text-primary');
+ $watch.find('.to-watch h4').addClass('text-primary');
+ $watch.find('.fa-eye-slash').removeClass('fa-eye-slash').addClass('fa-eye');
+ $watch.removeClass("no-watching").addClass("watching");
+ }
+ });
+ return false;
+ }).on('click', '.to-unwatch', function () {
+ if ($watch.hasClass("no-watching")) {
+ return false;
+ }
+ $.get(unwatchLink, function (json) {
+ if (json.ok) {
+ $watch.find('.text-primary').removeClass('text-primary');
+ $watch.find('.to-unwatch h4').addClass('text-primary');
+ $watch.find('.fa-eye').removeClass('fa-eye').addClass('fa-eye-slash');
+ $watch.removeClass("watching").addClass("no-watching");
+ }
+ });
+ return false;
+ });
+ })();
+}
+
(function ($) {
$(function () {
initCore();
@@ -152,5 +251,8 @@ function initUserSetting() {
if (body.data("page") == "user") {
initUserSetting();
}
+ if ($('.gogs-repo-nav').length) {
+ initRepository();
+ }
});
})(jQuery);
diff --git a/public/js/lib.js b/public/js/lib.js
index 2a98f2777..b5cc41c04 100644
--- a/public/js/lib.js
+++ b/public/js/lib.js
@@ -340,7 +340,7 @@ q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?
s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
@@ -400,7 +400,7 @@ PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n\u000
["kwd",/^-[_a-z]+/],["typ",/^[A-Z_]\w*/],["pun",/^[,.;]/]]),["erlang","erl"]);
// lang-go.js
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["pln",/^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])+(?:'|$)|`[^`]*(?:`|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\n\r]*|\/\*[\S\s]*?\*\/)/],["pln",/^(?:[^"'/`]|\/(?![*/]))+/]]),["go"]);
+// PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["pln",/^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])+(?:'|$)|`[^`]*(?:`|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\n\r]*|\/\*[\S\s]*?\*\/)/],["pln",/^(?:[^"'/`]|\/(?![*/]))+/]]),["go"]);
// lang-hs.js
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n\u000b\u000c\r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/,
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
new file mode 100644
index 000000000..2e19b99c1
--- /dev/null
+++ b/routers/admin/admin.go
@@ -0,0 +1,77 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package admin
+
+import (
+ "strings"
+
+ "github.com/codegangsta/martini"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/modules/base"
+ "github.com/gogits/gogs/modules/middleware"
+)
+
+func Dashboard(ctx *middleware.Context) {
+ ctx.Data["Title"] = "Admin Dashboard"
+ ctx.Data["PageIsDashboard"] = true
+ ctx.Data["Stats"] = models.GetStatistic()
+ ctx.HTML(200, "admin/dashboard")
+}
+
+func Users(ctx *middleware.Context) {
+ ctx.Data["Title"] = "User Management"
+ ctx.Data["PageIsUsers"] = true
+
+ var err error
+ ctx.Data["Users"], err = models.GetUsers(100, 0)
+ if err != nil {
+ ctx.Handle(200, "admin.Users", err)
+ return
+ }
+ ctx.HTML(200, "admin/users")
+}
+
+func Repositories(ctx *middleware.Context) {
+ ctx.Data["Title"] = "Repository Management"
+ ctx.Data["PageIsRepos"] = true
+
+ var err error
+ ctx.Data["Repos"], err = models.GetRepos(100, 0)
+ if err != nil {
+ ctx.Handle(200, "admin.Repositories", err)
+ return
+ }
+ ctx.HTML(200, "admin/repos")
+}
+
+func Config(ctx *middleware.Context) {
+ ctx.Data["Title"] = "Server Configuration"
+ ctx.Data["PageIsConfig"] = true
+
+ ctx.Data["AppUrl"] = base.AppUrl
+ ctx.Data["Domain"] = base.Domain
+ ctx.Data["RunUser"] = base.RunUser
+ ctx.Data["RunMode"] = strings.Title(martini.Env)
+ ctx.Data["RepoRootPath"] = base.RepoRootPath
+
+ ctx.Data["Service"] = base.Service
+
+ ctx.Data["DbCfg"] = models.DbCfg
+
+ ctx.Data["MailerEnabled"] = false
+ if base.MailService != nil {
+ ctx.Data["MailerEnabled"] = true
+ ctx.Data["Mailer"] = base.MailService
+ }
+
+ ctx.Data["CacheAdapter"] = base.CacheAdapter
+ ctx.Data["CacheConfig"] = base.CacheConfig
+
+ ctx.Data["LogMode"] = base.LogMode
+ ctx.Data["LogConfig"] = base.LogConfig
+
+ ctx.HTML(200, "admin/config")
+}
diff --git a/routers/admin/user.go b/routers/admin/user.go
new file mode 100644
index 000000000..d6f852321
--- /dev/null
+++ b/routers/admin/user.go
@@ -0,0 +1,109 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package admin
+
+import (
+ "strings"
+
+ "github.com/codegangsta/martini"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/modules/auth"
+ "github.com/gogits/gogs/modules/base"
+ "github.com/gogits/gogs/modules/log"
+ "github.com/gogits/gogs/modules/middleware"
+)
+
+func NewUser(ctx *middleware.Context, form auth.RegisterForm) {
+ ctx.Data["Title"] = "New Account"
+ ctx.Data["PageIsUsers"] = true
+
+ if ctx.Req.Method == "GET" {
+ ctx.HTML(200, "admin/users/new")
+ return
+ }
+
+ if form.Password != form.RetypePasswd {
+ ctx.Data["HasError"] = true
+ ctx.Data["Err_Password"] = true
+ ctx.Data["Err_RetypePasswd"] = true
+ ctx.Data["ErrorMsg"] = "Password and re-type password are not same"
+ auth.AssignForm(form, ctx.Data)
+ }
+
+ if ctx.HasError() {
+ ctx.HTML(200, "admin/users/new")
+ return
+ }
+
+ u := &models.User{
+ Name: form.UserName,
+ Email: form.Email,
+ Passwd: form.Password,
+ IsActive: true,
+ }
+
+ var err error
+ if u, err = models.RegisterUser(u); err != nil {
+ switch err {
+ case models.ErrUserAlreadyExist:
+ ctx.RenderWithErr("Username has been already taken", "admin/users/new", &form)
+ case models.ErrEmailAlreadyUsed:
+ ctx.RenderWithErr("E-mail address has been already used", "admin/users/new", &form)
+ case models.ErrUserNameIllegal:
+ ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "admin/users/new", &form)
+ default:
+ ctx.Handle(200, "admin.user.NewUser", err)
+ }
+ return
+ }
+
+ log.Trace("%s User created by admin(%s): %s", ctx.Req.RequestURI,
+ ctx.User.LowerName, strings.ToLower(form.UserName))
+
+ ctx.Redirect("/admin/users")
+}
+
+func EditUser(ctx *middleware.Context, params martini.Params, form auth.AdminEditUserForm) {
+ ctx.Data["Title"] = "Edit Account"
+ ctx.Data["PageIsUsers"] = true
+
+ uid, err := base.StrTo(params["userid"]).Int()
+ if err != nil {
+ ctx.Handle(200, "admin.user.EditUser", err)
+ return
+ }
+
+ u, err := models.GetUserById(int64(uid))
+ if err != nil {
+ ctx.Handle(200, "admin.user.EditUser", err)
+ return
+ }
+
+ if ctx.Req.Method == "GET" {
+ ctx.Data["User"] = u
+ ctx.HTML(200, "admin/users/edit")
+ return
+ }
+
+ u.Email = form.Email
+ u.Website = form.Website
+ u.Location = form.Location
+ u.Avatar = base.EncodeMd5(form.Avatar)
+ u.AvatarEmail = form.Avatar
+ u.IsActive = form.Active == "on"
+ u.IsAdmin = form.Admin == "on"
+ if err := models.UpdateUser(u); err != nil {
+ ctx.Handle(200, "admin.user.EditUser", err)
+ return
+ }
+
+ ctx.Data["IsSuccess"] = true
+ ctx.Data["User"] = u
+ ctx.HTML(200, "admin/users/edit")
+
+ log.Trace("%s User profile updated by admin(%s): %s", ctx.Req.RequestURI,
+ ctx.User.LowerName, ctx.User.LowerName)
+}
diff --git a/routers/dashboard.go b/routers/dashboard.go
index 6c194ad9e..f61d67b7d 100644
--- a/routers/dashboard.go
+++ b/routers/dashboard.go
@@ -15,10 +15,10 @@ func Home(ctx *middleware.Context) {
return
}
ctx.Data["PageIsHome"] = true
- ctx.HTML(200, "home", ctx.Data)
+ ctx.HTML(200, "home")
}
func Help(ctx *middleware.Context) {
ctx.Data["PageIsHelp"] = true
- ctx.HTML(200, "help", ctx.Data)
+ ctx.HTML(200, "help")
}
diff --git a/routers/dev/template.go b/routers/dev/template.go
index 7d5225ece..d2f77ac4d 100644
--- a/routers/dev/template.go
+++ b/routers/dev/template.go
@@ -21,5 +21,5 @@ func TemplatePreview(ctx *middleware.Context, params martini.Params) {
ctx.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374"
ctx.Data["ActiveCodeLives"] = base.Service.ActiveCodeLives / 60
ctx.Data["ResetPwdCodeLives"] = base.Service.ResetPwdCodeLives / 60
- ctx.HTML(200, params["_1"], ctx.Data)
+ ctx.HTML(200, params["_1"])
}
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index b38473b18..c83a6df52 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -18,7 +18,7 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
ctx.Data["Licenses"] = models.Licenses
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "repo/create", ctx.Data)
+ ctx.HTML(200, "repo/create")
return
}
@@ -31,6 +31,9 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
} else if err == models.ErrRepoAlreadyExist {
ctx.RenderWithErr("Repository name has already been used", "repo/create", &form)
return
+ } else if err == models.ErrRepoNameIllegal {
+ ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form)
+ return
}
ctx.Handle(200, "repo.Create", err)
}
@@ -45,7 +48,7 @@ func SettingPost(ctx *middleware.Context) {
case "delete":
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct."
- ctx.HTML(200, "repo/setting", ctx.Data)
+ ctx.HTML(200, "repo/setting")
return
}
diff --git a/routers/repo/single.go b/routers/repo/single.go
index 064150a23..37c0fabd7 100644
--- a/routers/repo/single.go
+++ b/routers/repo/single.go
@@ -40,7 +40,7 @@ func Branches(ctx *middleware.Context, params martini.Params) {
ctx.Data["Branches"] = brs
ctx.Data["IsRepoToolbarBranches"] = true
- ctx.HTML(200, "repo/branches", ctx.Data)
+ ctx.HTML(200, "repo/branches")
}
func Single(ctx *middleware.Context, params martini.Params) {
@@ -61,6 +61,8 @@ func Single(ctx *middleware.Context, params martini.Params) {
return
}
+ ctx.Data["IsRepoToolbarSource"] = true
+
// Branches.
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
@@ -69,7 +71,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
return
} else if len(brs) == 0 {
ctx.Data["IsBareRepo"] = true
- ctx.HTML(200, "repo/single", ctx.Data)
+ ctx.HTML(200, "repo/single")
return
}
@@ -86,6 +88,11 @@ func Single(ctx *middleware.Context, params martini.Params) {
branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + params["branchname"]
+ if len(treename) != 0 && repoFile == nil {
+ ctx.Error(404)
+ return
+ }
+
if repoFile != nil && repoFile.IsFile() {
if repoFile.Size > 1024*1024 || repoFile.Filemode != git.FileModeBlob {
ctx.Data["FileIsLarge"] = true
@@ -95,6 +102,11 @@ func Single(ctx *middleware.Context, params martini.Params) {
} else {
ctx.Data["IsFile"] = true
ctx.Data["FileName"] = repoFile.Name
+ ext := path.Ext(repoFile.Name)
+ if len(ext) > 0 {
+ ext = ext[1:]
+ }
+ ctx.Data["FileExt"] = ext
readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name)
ctx.Data["ReadmeExist"] = readmeExist
@@ -139,10 +151,9 @@ func Single(ctx *middleware.Context, params martini.Params) {
return
} else {
// current repo branch link
- urlPrefix := "http://" + base.Domain + branchLink
ctx.Data["FileName"] = readmeFile.Name
- ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), urlPrefix))
+ ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), branchLink))
}
}
}
@@ -178,9 +189,8 @@ func Single(ctx *middleware.Context, params martini.Params) {
ctx.Data["Paths"] = Paths
ctx.Data["Treenames"] = treenames
- ctx.Data["IsRepoToolbarSource"] = true
ctx.Data["BranchLink"] = branchLink
- ctx.HTML(200, "repo/single", ctx.Data)
+ ctx.HTML(200, "repo/single")
}
func Http(ctx *middleware.Context, params martini.Params) {
@@ -212,6 +222,8 @@ func Setting(ctx *middleware.Context, params martini.Params) {
return
}
+ ctx.Data["IsRepoToolbarSetting"] = true
+
// Branches.
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
@@ -220,7 +232,7 @@ func Setting(ctx *middleware.Context, params martini.Params) {
return
} else if len(brs) == 0 {
ctx.Data["IsBareRepo"] = true
- ctx.HTML(200, "repo/setting", ctx.Data)
+ ctx.HTML(200, "repo/setting")
return
}
@@ -229,9 +241,13 @@ func Setting(ctx *middleware.Context, params martini.Params) {
title = t
}
+ if len(params["branchname"]) == 0 {
+ params["branchname"] = "master"
+ }
+
+ ctx.Data["Branchname"] = params["branchname"]
ctx.Data["Title"] = title + " - settings"
- ctx.Data["IsRepoToolbarSetting"] = true
- ctx.HTML(200, "repo/setting", ctx.Data)
+ ctx.HTML(200, "repo/setting")
}
func Commits(ctx *middleware.Context, params martini.Params) {
@@ -255,17 +271,17 @@ func Commits(ctx *middleware.Context, params martini.Params) {
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["Commits"] = commits
- ctx.HTML(200, "repo/commits", ctx.Data)
+ ctx.HTML(200, "repo/commits")
}
func Issues(ctx *middleware.Context) {
ctx.Data["IsRepoToolbarIssues"] = true
- ctx.HTML(200, "repo/issues", ctx.Data)
+ ctx.HTML(200, "repo/issues")
}
func Pulls(ctx *middleware.Context) {
ctx.Data["IsRepoToolbarPulls"] = true
- ctx.HTML(200, "repo/pulls", ctx.Data)
+ ctx.HTML(200, "repo/pulls")
}
func Action(ctx *middleware.Context, params martini.Params) {
diff --git a/routers/user/setting.go b/routers/user/setting.go
index 053f327f0..75adf2b81 100644
--- a/routers/user/setting.go
+++ b/routers/user/setting.go
@@ -24,13 +24,13 @@ func Setting(ctx *middleware.Context, form auth.UpdateProfileForm) {
ctx.Data["Owner"] = user
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "user/setting", ctx.Data)
+ ctx.HTML(200, "user/setting")
return
}
// below is for POST requests
if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
- ctx.HTML(200, "user/setting", ctx.Data)
+ ctx.HTML(200, "user/setting")
return
}
@@ -45,7 +45,8 @@ func Setting(ctx *middleware.Context, form auth.UpdateProfileForm) {
}
ctx.Data["IsSuccess"] = true
- ctx.HTML(200, "user/setting", ctx.Data)
+ ctx.HTML(200, "user/setting")
+
log.Trace("%s User setting updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
}
@@ -55,7 +56,7 @@ func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {
ctx.Data["IsUserPageSettingPasswd"] = true
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "user/password", ctx.Data)
+ ctx.HTML(200, "user/password")
return
}
@@ -82,7 +83,7 @@ func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {
}
ctx.Data["Owner"] = user
- ctx.HTML(200, "user/password", ctx.Data)
+ ctx.HTML(200, "user/password")
log.Trace("%s User password updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
}
@@ -123,7 +124,7 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) {
// Add new SSH key.
if ctx.Req.Method == "POST" {
if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
- ctx.HTML(200, "user/publickey", ctx.Data)
+ ctx.HTML(200, "user/publickey")
return
}
@@ -155,7 +156,7 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) {
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingSSH"] = true
ctx.Data["Keys"] = keys
- ctx.HTML(200, "user/publickey", ctx.Data)
+ ctx.HTML(200, "user/publickey")
}
func SettingNotification(ctx *middleware.Context) {
@@ -163,7 +164,7 @@ func SettingNotification(ctx *middleware.Context) {
ctx.Data["Title"] = "Notification"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingNotify"] = true
- ctx.HTML(200, "user/notification", ctx.Data)
+ ctx.HTML(200, "user/notification")
}
func SettingSecurity(ctx *middleware.Context) {
@@ -171,5 +172,5 @@ func SettingSecurity(ctx *middleware.Context) {
ctx.Data["Title"] = "Security"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingSecurity"] = true
- ctx.HTML(200, "user/security", ctx.Data)
+ ctx.HTML(200, "user/security")
}
diff --git a/routers/user/user.go b/routers/user/user.go
index f495cb13a..d38eb1ceb 100644
--- a/routers/user/user.go
+++ b/routers/user/user.go
@@ -34,7 +34,7 @@ func Dashboard(ctx *middleware.Context) {
return
}
ctx.Data["Feeds"] = feeds
- ctx.HTML(200, "user/dashboard", ctx.Data)
+ ctx.HTML(200, "user/dashboard")
}
func Profile(ctx *middleware.Context, params martini.Params) {
@@ -70,19 +70,19 @@ func Profile(ctx *middleware.Context, params martini.Params) {
}
ctx.Data["PageIsUserProfile"] = true
- ctx.HTML(200, "user/profile", ctx.Data)
+ ctx.HTML(200, "user/profile")
}
func SignIn(ctx *middleware.Context, form auth.LogInForm) {
ctx.Data["Title"] = "Log In"
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "user/signin", ctx.Data)
+ ctx.HTML(200, "user/signin")
return
}
if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
- ctx.HTML(200, "user/signin", ctx.Data)
+ ctx.HTML(200, "user/signin")
return
}
@@ -112,8 +112,14 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
ctx.Data["Title"] = "Sign Up"
ctx.Data["PageIsSignUp"] = true
+ if base.Service.DisenableRegisteration {
+ ctx.Data["DisenableRegisteration"] = true
+ ctx.HTML(200, "user/signup")
+ return
+ }
+
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "user/signup", ctx.Data)
+ ctx.HTML(200, "user/signup")
return
}
@@ -126,7 +132,7 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
}
if ctx.HasError() {
- ctx.HTML(200, "user/signup", ctx.Data)
+ ctx.HTML(200, "user/signup")
return
}
@@ -139,11 +145,13 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
var err error
if u, err = models.RegisterUser(u); err != nil {
- switch err.Error() {
- case models.ErrUserAlreadyExist.Error():
+ switch err {
+ case models.ErrUserAlreadyExist:
ctx.RenderWithErr("Username has been already taken", "user/signup", &form)
- case models.ErrEmailAlreadyUsed.Error():
+ case models.ErrEmailAlreadyUsed:
ctx.RenderWithErr("E-mail address has been already used", "user/signup", &form)
+ case models.ErrUserNameIllegal:
+ ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "user/signup", &form)
default:
ctx.Handle(200, "user.SignUp", err)
}
@@ -153,12 +161,16 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
log.Trace("%s User created: %s", ctx.Req.RequestURI, strings.ToLower(form.UserName))
// Send confirmation e-mail.
- if base.Service.RegisterEmailConfirm {
+ if base.Service.RegisterEmailConfirm && u.Id > 1 {
mailer.SendRegisterMail(ctx.Render, u)
ctx.Data["IsSendRegisterMail"] = true
ctx.Data["Email"] = u.Email
ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
- ctx.Render.HTML(200, "user/active", ctx.Data)
+ ctx.HTML(200, "user/active")
+
+ if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
+ log.Error("Set cache(MailResendLimit) fail: %v", err)
+ }
return
}
ctx.Redirect("/user/login")
@@ -170,7 +182,7 @@ func Delete(ctx *middleware.Context) {
ctx.Data["IsUserPageSettingDelete"] = true
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "user/delete", ctx.Data)
+ ctx.HTML(200, "user/delete")
return
}
@@ -195,7 +207,7 @@ func Delete(ctx *middleware.Context) {
}
}
- ctx.HTML(200, "user/delete", ctx.Data)
+ ctx.HTML(200, "user/delete")
}
const (
@@ -218,15 +230,15 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
}
func Issues(ctx *middleware.Context) {
- ctx.HTML(200, "user/issues", ctx.Data)
+ ctx.HTML(200, "user/issues")
}
func Pulls(ctx *middleware.Context) {
- ctx.HTML(200, "user/pulls", ctx.Data)
+ ctx.HTML(200, "user/pulls")
}
func Stars(ctx *middleware.Context) {
- ctx.HTML(200, "user/stars", ctx.Data)
+ ctx.HTML(200, "user/stars")
}
func Activate(ctx *middleware.Context) {
@@ -239,12 +251,16 @@ func Activate(ctx *middleware.Context) {
}
// Resend confirmation e-mail.
if base.Service.RegisterEmailConfirm {
- ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
- mailer.SendActiveMail(ctx.Render, ctx.User)
+ if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) {
+ ctx.Data["ResendLimited"] = true
+ } else {
+ ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
+ mailer.SendActiveMail(ctx.Render, ctx.User)
+ }
} else {
ctx.Data["ServiceNotEnabled"] = true
}
- ctx.Render.HTML(200, "user/active", ctx.Data)
+ ctx.HTML(200, "user/active")
return
}
@@ -263,5 +279,5 @@ func Activate(ctx *middleware.Context) {
}
ctx.Data["IsActivateFailed"] = true
- ctx.Render.HTML(200, "user/active", ctx.Data)
+ ctx.HTML(200, "user/active")
}
diff --git a/serve.go b/serve.go
index ddc670235..3ce8f9046 100644
--- a/serve.go
+++ b/serve.go
@@ -12,7 +12,9 @@ import (
"strings"
"github.com/codegangsta/cli"
+
"github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/modules/base"
)
var (
@@ -43,6 +45,10 @@ func In(b string, sl map[string]int) bool {
}
func runServ(*cli.Context) {
+ base.NewConfigContext()
+ models.LoadModelsConfig()
+ models.NewEngine()
+
keys := strings.Split(os.Args[2], "-")
if len(keys) != 2 {
fmt.Println("auth file format error")
@@ -144,7 +150,7 @@ func runServ(*cli.Context) {
}
gitcmd := exec.Command(verb, rRepo)
- gitcmd.Dir = models.RepoRootPath
+ gitcmd.Dir = base.RepoRootPath
gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
new file mode 100644
index 000000000..6906f2409
--- /dev/null
+++ b/templates/admin/config.tmpl
@@ -0,0 +1,93 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+
+ {{template "admin/nav" .}}
+
+
+
+ Server Configuration
+
+
+
+
Application Name: {{AppName}}
+
Application Version: {{AppVer}}
+
Application URL: {{.AppUrl}}
+
Domain: {{.Domain}}
+
+
Run User: {{.RunUser}}
+
Run Mode: {{.RunMode}}
+
+
Repository Root Path: {{.RepoRootPath}}
+
+
+
+
+
+ Database Configuration
+
+
+
+
Type: {{.DbCfg.Type}}
+
Host: {{.DbCfg.Host}}
+
Name: {{.DbCfg.Name}}
+
User: {{.DbCfg.User}}
+
SslMode: {{.DbCfg.SslMode}} (for "postgres" only)
+
Path: {{.DbCfg.Path}} (for "sqlite3" only)
+
+
+
+
+
+ Service Configuration
+
+
+
+
Register Email Confirmation:
+
Disenable Registeration:
+
Require Sign In View:
+
+
Active Code Lives: {{.Service.ActiveCodeLives}} minutes
+
Reset Password Code Lives: {{.Service.ResetPwdCodeLives}} minutes
+
+
+
+
+
+ Mailer Configuration
+
+
+
+
Enabled:
+
Name: {{.Mailer.Name}}
+
Host: {{.Mailer.Host}}
+
User: {{.Mailer.User}}
+
+
+
+
+
+ Cache Configuration
+
+
+
+
Cache Adapter: {{.CacheAdapter}}
+
Cache Config:
+
+
+
+
+
+
+ Log Configuration
+
+
+
+
Log Mode: {{.LogMode}}
+
Log Config:
+
+
+
+
+
+
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
new file mode 100644
index 000000000..6088487d6
--- /dev/null
+++ b/templates/admin/dashboard.tmpl
@@ -0,0 +1,26 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+
+ {{template "admin/nav" .}}
+
+
+
+ Statistic
+
+
+
+ Gogs database has {{.Stats.Counter.User}} users, {{.Stats.Counter.PublicKey}} SSH keys, {{.Stats.Counter.Repo}} repositories, {{.Stats.Counter.Watch}} watches, {{.Stats.Counter.Action}} actions, and {{.Stats.Counter.Access}} accesses.
+
+
+
+
+
+ System Status
+
+
+
+
+
+
+
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/admin/nav.tmpl b/templates/admin/nav.tmpl
new file mode 100644
index 000000000..72f008acc
--- /dev/null
+++ b/templates/admin/nav.tmpl
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/templates/admin/repos.tmpl b/templates/admin/repos.tmpl
new file mode 100644
index 000000000..a1f41d836
--- /dev/null
+++ b/templates/admin/repos.tmpl
@@ -0,0 +1,42 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+
+ {{template "admin/nav" .}}
+
+
+
+ Repository Management
+
+
+
+
+
+
+ Id |
+ Owner |
+ Name |
+ Private |
+ Watches |
+ Forks |
+ Created |
+
+
+
+ {{range .Repos}}
+
+ {{.Id}} |
+ {{.UserName}} |
+ {{.Name}} |
+ |
+ {{.NumWatches}} |
+ {{.NumForks}} |
+ {{DateFormat .Created "M d, Y"}} |
+
+ {{end}}
+
+
+
+
+
+
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/admin/users.tmpl b/templates/admin/users.tmpl
new file mode 100644
index 000000000..dec5c1895
--- /dev/null
+++ b/templates/admin/users.tmpl
@@ -0,0 +1,45 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+
+ {{template "admin/nav" .}}
+
+
+
+ User Management
+
+
+
+
New Account
+
+
+
+ Id |
+ Name |
+ E-mail |
+ Actived |
+ Admin |
+ Repos |
+ Join |
+ Edit |
+
+
+
+ {{range .Users}}
+
+ {{.Id}} |
+ {{.Name}} |
+ {{.Email}} |
+ |
+ |
+ {{.NumRepos}} |
+ {{DateFormat .Created "M d, Y"}} |
+ |
+
+ {{end}}
+
+
+
+
+
+
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/admin/users/edit.tmpl b/templates/admin/users/edit.tmpl
new file mode 100644
index 000000000..415bcedc9
--- /dev/null
+++ b/templates/admin/users/edit.tmpl
@@ -0,0 +1,83 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+
+ {{template "admin/nav" .}}
+
+
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/admin/users/new.tmpl b/templates/admin/users/new.tmpl
new file mode 100644
index 000000000..01d976caa
--- /dev/null
+++ b/templates/admin/users/new.tmpl
@@ -0,0 +1,54 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+
+ {{template "admin/nav" .}}
+
+
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl
index fe0ea9302..6c2da63e5 100644
--- a/templates/base/footer.tmpl
+++ b/templates/base/footer.tmpl
@@ -2,12 +2,19 @@