mirror of https://github.com/gogits/gogs.git
Baire Adams
7 years ago
committed by
GitHub
1944 changed files with 885328 additions and 33788 deletions
@ -1,19 +1,21 @@ |
|||||||
[run] |
[run] |
||||||
init_cmds = [ |
init_cmds = [ |
||||||
["make", "build-dev", "TAGS=sqlite"], |
["make", "build-dev"], |
||||||
["./gogs", "web"] |
["./gogs", "web"] |
||||||
] |
] |
||||||
watch_all = true |
watch_all = true |
||||||
watch_dirs = [ |
watch_dirs = [ |
||||||
"$WORKDIR/cmd", |
"$WORKDIR/cmd", |
||||||
"$WORKDIR/models", |
"$WORKDIR/models", |
||||||
"$WORKDIR/modules", |
"$WORKDIR/pkg", |
||||||
"$WORKDIR/routers" |
"$WORKDIR/routes" |
||||||
] |
] |
||||||
watch_exts = [".go"] |
watch_exts = [".go"] |
||||||
ignore_files = [".+_test.go"] |
ignore_files = [".+_test.go"] |
||||||
build_delay = 1500 |
build_delay = 1500 |
||||||
|
interrupt_timout = 1 |
||||||
|
graceful_kill = true |
||||||
cmds = [ |
cmds = [ |
||||||
["make", "build-dev", "TAGS=sqlite"], # cert pam tidb |
["make", "build-dev"], # TAGS=sqlite cert pam tidb |
||||||
["./gogs", "web"] |
["./gogs", "web"] |
||||||
] |
] |
@ -0,0 +1,7 @@ |
|||||||
|
conf/** |
||||||
|
docker/** |
||||||
|
modules/bindata/** |
||||||
|
packager/** |
||||||
|
public/** |
||||||
|
scripts/** |
||||||
|
templates/** |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"GOLANG": { |
||||||
|
"TOTAL_LOC": [500, 999, 1999, 9999], |
||||||
|
"TOO_MANY_FUNCTIONS": [50, 99, 199, 999], |
||||||
|
"TOO_MANY_IVARS": [20, 50, 70, 99] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
conf/gitignore/* linguist-vendored |
||||||
|
conf/license/* linguist-vendored |
||||||
|
public/assets/* linguist-vendored |
||||||
|
public/plugins/* linguist-vendored |
||||||
|
public/css/themes/* linguist-vendored |
||||||
|
public/css/github.min.css linguist-vendored |
||||||
|
public/css/semantic-2.2.10.min.css linguist-vendored |
||||||
|
public/js/libs/* linguist-vendored |
||||||
|
public/js/jquery-1.11.3.min.js linguist-vendored |
||||||
|
public/js/semantic-2.2.10.min.js linguist-vendored |
@ -1,4 +1,9 @@ |
|||||||
Please, make sure you are targeting the `develop` branch! |
The pull request will be closed without any reasons if it does not satisfy any of following requirements: |
||||||
|
|
||||||
More instructions about contributing with Gogs code can be found here: |
1. Please make sure you are targeting the `develop` branch. |
||||||
|
2. Please read contributing guidelines: |
||||||
https://github.com/gogits/gogs/wiki/Contributing-Code |
https://github.com/gogits/gogs/wiki/Contributing-Code |
||||||
|
3. Please describe what your pull request does and which issue you're targeting |
||||||
|
4. ... if it is not related to any particular issues, explain why we should not reject your pull request. |
||||||
|
|
||||||
|
**You MUST delete above content including this line before posting; too lazy to take this action considered invalid pull request.** |
||||||
|
@ -0,0 +1,2 @@ |
|||||||
|
Unknwon <u@gogs.io> <joe2010xtmf@163.com> |
||||||
|
Unknwon <u@gogs.io> 无闻 <u@gogs.io> |
@ -0,0 +1,20 @@ |
|||||||
|
version: "{build}" |
||||||
|
skip_tags: true |
||||||
|
clone_folder: c:\gopath\src\github.com\gogits\gogs |
||||||
|
clone_depth: 1 |
||||||
|
|
||||||
|
environment: |
||||||
|
GOPATH: c:\gopath |
||||||
|
GOVERSION: 1.7 |
||||||
|
|
||||||
|
build: false |
||||||
|
deploy: false |
||||||
|
|
||||||
|
install: |
||||||
|
- go build -v |
||||||
|
|
||||||
|
notifications: |
||||||
|
- provider: Email |
||||||
|
to: |
||||||
|
- u@gogs.io |
||||||
|
on_build_success: false |
@ -0,0 +1,183 @@ |
|||||||
|
// Copyright 2016 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 cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"runtime" |
||||||
|
|
||||||
|
"github.com/urfave/cli" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/models" |
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
Admin = cli.Command{ |
||||||
|
Name: "admin", |
||||||
|
Usage: "Perform admin operations on command line", |
||||||
|
Description: `Allow using internal logic of Gogs without hacking into the source code |
||||||
|
to make automatic initialization process more smoothly`, |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
subcmdCreateUser, |
||||||
|
subcmdDeleteInactivateUsers, |
||||||
|
subcmdDeleteRepositoryArchives, |
||||||
|
subcmdDeleteMissingRepositories, |
||||||
|
subcmdGitGcRepos, |
||||||
|
subcmdRewriteAllPublicKeys, |
||||||
|
subcmdSyncRepositoryHooks, |
||||||
|
subcmdReinitMissingRepositories, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdCreateUser = cli.Command{ |
||||||
|
Name: "create-user", |
||||||
|
Usage: "Create a new user in database", |
||||||
|
Action: runCreateUser, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("name", "", "Username"), |
||||||
|
stringFlag("password", "", "User password"), |
||||||
|
stringFlag("email", "", "User email address"), |
||||||
|
boolFlag("admin", "User is an admin"), |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdDeleteInactivateUsers = cli.Command{ |
||||||
|
Name: "delete-inactive-users", |
||||||
|
Usage: "Delete all inactive accounts", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.DeleteInactivateUsers, |
||||||
|
"All inactivate accounts have been deleted successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdDeleteRepositoryArchives = cli.Command{ |
||||||
|
Name: "delete-repository-archives", |
||||||
|
Usage: "Delete all repositories archives", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.DeleteRepositoryArchives, |
||||||
|
"All repositories archives have been deleted successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdDeleteMissingRepositories = cli.Command{ |
||||||
|
Name: "delete-missing-repositories", |
||||||
|
Usage: "Delete all repository records that lost Git files", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.DeleteMissingRepositories, |
||||||
|
"All repositories archives have been deleted successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdGitGcRepos = cli.Command{ |
||||||
|
Name: "collect-garbage", |
||||||
|
Usage: "Do garbage collection on repositories", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.GitGcRepos, |
||||||
|
"All repositories have done garbage collection successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdRewriteAllPublicKeys = cli.Command{ |
||||||
|
Name: "rewrite-public-keys", |
||||||
|
Usage: "Rewrite '.ssh/authorized_keys' file (caution: non-Gogs keys will be lost)", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.RewriteAllPublicKeys, |
||||||
|
"All public keys have been rewritten successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdSyncRepositoryHooks = cli.Command{ |
||||||
|
Name: "resync-hooks", |
||||||
|
Usage: "Resync pre-receive, update and post-receive hooks", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.SyncRepositoryHooks, |
||||||
|
"All repositories' pre-receive, update and post-receive hooks have been resynced successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdReinitMissingRepositories = cli.Command{ |
||||||
|
Name: "reinit-missing-repositories", |
||||||
|
Usage: "Reinitialize all repository records that lost Git files", |
||||||
|
Action: adminDashboardOperation( |
||||||
|
models.ReinitMissingRepositories, |
||||||
|
"All repository records that lost Git files have been reinitialized successfully", |
||||||
|
), |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func runCreateUser(c *cli.Context) error { |
||||||
|
if !c.IsSet("name") { |
||||||
|
return fmt.Errorf("Username is not specified") |
||||||
|
} else if !c.IsSet("password") { |
||||||
|
return fmt.Errorf("Password is not specified") |
||||||
|
} else if !c.IsSet("email") { |
||||||
|
return fmt.Errorf("Email is not specified") |
||||||
|
} |
||||||
|
|
||||||
|
if c.IsSet("config") { |
||||||
|
setting.CustomConf = c.String("config") |
||||||
|
} |
||||||
|
|
||||||
|
setting.NewContext() |
||||||
|
models.LoadConfigs() |
||||||
|
models.SetEngine() |
||||||
|
|
||||||
|
if err := models.CreateUser(&models.User{ |
||||||
|
Name: c.String("name"), |
||||||
|
Email: c.String("email"), |
||||||
|
Passwd: c.String("password"), |
||||||
|
IsActive: true, |
||||||
|
IsAdmin: c.Bool("admin"), |
||||||
|
}); err != nil { |
||||||
|
return fmt.Errorf("CreateUser: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Printf("New user '%s' has been successfully created!\n", c.String("name")) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func adminDashboardOperation(operation func() error, successMessage string) func(*cli.Context) error { |
||||||
|
return func(c *cli.Context) error { |
||||||
|
if c.IsSet("config") { |
||||||
|
setting.CustomConf = c.String("config") |
||||||
|
} |
||||||
|
|
||||||
|
setting.NewContext() |
||||||
|
models.LoadConfigs() |
||||||
|
models.SetEngine() |
||||||
|
|
||||||
|
if err := operation(); err != nil { |
||||||
|
functionName := runtime.FuncForPC(reflect.ValueOf(operation).Pointer()).Name() |
||||||
|
return fmt.Errorf("%s: %v", functionName, err) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Printf("%s\n", successMessage) |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
// Copyright 2017 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 cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/Unknwon/cae/zip" |
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/urfave/cli" |
||||||
|
log "gopkg.in/clog.v1" |
||||||
|
"gopkg.in/ini.v1" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/models" |
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
var Backup = cli.Command{ |
||||||
|
Name: "backup", |
||||||
|
Usage: "Backup files and database", |
||||||
|
Description: `Backup dumps and compresses all related files and database into zip file, |
||||||
|
which can be used for migrating Gogs to another server. The output format is meant to be |
||||||
|
portable among all supported database engines.`, |
||||||
|
Action: runBackup, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
boolFlag("verbose, v", "Show process details"), |
||||||
|
stringFlag("tempdir, t", os.TempDir(), "Temporary directory path"), |
||||||
|
stringFlag("target", "./", "Target directory path to save backup archive"), |
||||||
|
stringFlag("archive-name", fmt.Sprintf("gogs-backup-%s.zip", time.Now().Format("20060102150405")), "Name of backup archive"), |
||||||
|
boolFlag("database-only", "Only dump database"), |
||||||
|
boolFlag("exclude-repos", "Exclude repositories"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const _ARCHIVE_ROOT_DIR = "gogs-backup" |
||||||
|
|
||||||
|
func runBackup(c *cli.Context) error { |
||||||
|
zip.Verbose = c.Bool("verbose") |
||||||
|
if c.IsSet("config") { |
||||||
|
setting.CustomConf = c.String("config") |
||||||
|
} |
||||||
|
setting.NewContext() |
||||||
|
models.LoadConfigs() |
||||||
|
models.SetEngine() |
||||||
|
|
||||||
|
tmpDir := c.String("tempdir") |
||||||
|
if !com.IsExist(tmpDir) { |
||||||
|
log.Fatal(0, "'--tempdir' does not exist: %s", tmpDir) |
||||||
|
} |
||||||
|
rootDir, err := ioutil.TempDir(tmpDir, "gogs-backup-") |
||||||
|
if err != nil { |
||||||
|
log.Fatal(0, "Fail to create backup root directory '%s': %v", rootDir, err) |
||||||
|
} |
||||||
|
log.Info("Backup root directory: %s", rootDir) |
||||||
|
|
||||||
|
// Metadata
|
||||||
|
metaFile := path.Join(rootDir, "metadata.ini") |
||||||
|
metadata := ini.Empty() |
||||||
|
metadata.Section("").Key("VERSION").SetValue("1") |
||||||
|
metadata.Section("").Key("DATE_TIME").SetValue(time.Now().String()) |
||||||
|
metadata.Section("").Key("GOGS_VERSION").SetValue(setting.AppVer) |
||||||
|
if err = metadata.SaveTo(metaFile); err != nil { |
||||||
|
log.Fatal(0, "Fail to save metadata '%s': %v", metaFile, err) |
||||||
|
} |
||||||
|
|
||||||
|
archiveName := path.Join(c.String("target"), c.String("archive-name")) |
||||||
|
log.Info("Packing backup files to: %s", archiveName) |
||||||
|
|
||||||
|
z, err := zip.Create(archiveName) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(0, "Fail to create backup archive '%s': %v", archiveName, err) |
||||||
|
} |
||||||
|
if err = z.AddFile(_ARCHIVE_ROOT_DIR+"/metadata.ini", metaFile); err != nil { |
||||||
|
log.Fatal(0, "Fail to include 'metadata.ini': %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Database
|
||||||
|
dbDir := path.Join(rootDir, "db") |
||||||
|
if err = models.DumpDatabase(dbDir); err != nil { |
||||||
|
log.Fatal(0, "Fail to dump database: %v", err) |
||||||
|
} |
||||||
|
if err = z.AddDir(_ARCHIVE_ROOT_DIR+"/db", dbDir); err != nil { |
||||||
|
log.Fatal(0, "Fail to include 'db': %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Custom files
|
||||||
|
if !c.Bool("database-only") { |
||||||
|
if err = z.AddDir(_ARCHIVE_ROOT_DIR+"/custom", setting.CustomPath); err != nil { |
||||||
|
log.Fatal(0, "Fail to include 'custom': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Data files
|
||||||
|
if !c.Bool("database-only") { |
||||||
|
for _, dir := range []string{"attachments", "avatars"} { |
||||||
|
dirPath := path.Join(setting.AppDataPath, dir) |
||||||
|
if !com.IsDir(dirPath) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err = z.AddDir(path.Join(_ARCHIVE_ROOT_DIR+"/data", dir), dirPath); err != nil { |
||||||
|
log.Fatal(0, "Fail to include 'data': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Repositories
|
||||||
|
if !c.Bool("exclude-repos") && !c.Bool("database-only") { |
||||||
|
reposDump := path.Join(rootDir, "repositories.zip") |
||||||
|
log.Info("Dumping repositories in '%s'", setting.RepoRootPath) |
||||||
|
if err = zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil { |
||||||
|
log.Fatal(0, "Fail to dump repositories: %v", err) |
||||||
|
} |
||||||
|
log.Info("Repositories dumped to: %s", reposDump) |
||||||
|
|
||||||
|
if err = z.AddFile(_ARCHIVE_ROOT_DIR+"/repositories.zip", reposDump); err != nil { |
||||||
|
log.Fatal(0, "Fail to include 'repositories.zip': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if err = z.Close(); err != nil { |
||||||
|
log.Fatal(0, "Fail to save backup archive '%s': %v", archiveName, err) |
||||||
|
} |
||||||
|
|
||||||
|
os.RemoveAll(rootDir) |
||||||
|
log.Info("Backup succeed! Archive is located at: %s", archiveName) |
||||||
|
log.Shutdown() |
||||||
|
return nil |
||||||
|
} |
@ -1,97 +0,0 @@ |
|||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"os" |
|
||||||
"path" |
|
||||||
"time" |
|
||||||
|
|
||||||
"io/ioutil" |
|
||||||
|
|
||||||
"github.com/Unknwon/cae/zip" |
|
||||||
"github.com/codegangsta/cli" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/models" |
|
||||||
"github.com/gogits/gogs/modules/setting" |
|
||||||
) |
|
||||||
|
|
||||||
var CmdDump = cli.Command{ |
|
||||||
Name: "dump", |
|
||||||
Usage: "Dump Gogs files and database", |
|
||||||
Description: `Dump compresses all related files and database into zip file. |
|
||||||
It can be used for backup and capture Gogs server image to send to maintainer`, |
|
||||||
Action: runDump, |
|
||||||
Flags: []cli.Flag{ |
|
||||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
|
||||||
boolFlag("verbose, v", "show process details"), |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
func runDump(ctx *cli.Context) { |
|
||||||
if ctx.IsSet("config") { |
|
||||||
setting.CustomConf = ctx.String("config") |
|
||||||
} |
|
||||||
setting.NewContext() |
|
||||||
models.LoadConfigs() |
|
||||||
models.SetEngine() |
|
||||||
|
|
||||||
TmpWorkDir, err := ioutil.TempDir(os.TempDir(), "gogs-dump-") |
|
||||||
if err != nil { |
|
||||||
log.Fatalf("Fail to create tmp work directory: %v", err) |
|
||||||
} |
|
||||||
log.Printf("Creating tmp work dir: %s", TmpWorkDir) |
|
||||||
|
|
||||||
reposDump := path.Join(TmpWorkDir, "gogs-repo.zip") |
|
||||||
dbDump := path.Join(TmpWorkDir, "gogs-db.sql") |
|
||||||
|
|
||||||
log.Printf("Dumping local repositories...%s", setting.RepoRootPath) |
|
||||||
zip.Verbose = ctx.Bool("verbose") |
|
||||||
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil { |
|
||||||
log.Fatalf("Fail to dump local repositories: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
log.Printf("Dumping database...") |
|
||||||
if err := models.DumpDatabase(dbDump); err != nil { |
|
||||||
log.Fatalf("Fail to dump database: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
fileName := fmt.Sprintf("gogs-dump-%d.zip", time.Now().Unix()) |
|
||||||
log.Printf("Packing dump files...") |
|
||||||
z, err := zip.Create(fileName) |
|
||||||
if err != nil { |
|
||||||
os.Remove(fileName) |
|
||||||
log.Fatalf("Fail to create %s: %v", fileName, err) |
|
||||||
} |
|
||||||
|
|
||||||
if err := z.AddFile("gogs-repo.zip", reposDump); err !=nil { |
|
||||||
log.Fatalf("Fail to include gogs-repo.zip: %v", err) |
|
||||||
} |
|
||||||
if err := z.AddFile("gogs-db.sql", dbDump); err !=nil { |
|
||||||
log.Fatalf("Fail to include gogs-db.sql: %v", err) |
|
||||||
} |
|
||||||
customDir, err := os.Stat(setting.CustomPath) |
|
||||||
if err == nil && customDir.IsDir() { |
|
||||||
if err := z.AddDir("custom", setting.CustomPath); err !=nil { |
|
||||||
log.Fatalf("Fail to include custom: %v", err) |
|
||||||
} |
|
||||||
} else { |
|
||||||
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath) |
|
||||||
} |
|
||||||
if err := z.AddDir("log", setting.LogRootPath); err !=nil { |
|
||||||
log.Fatalf("Fail to include log: %v", err) |
|
||||||
} |
|
||||||
// FIXME: SSH key file.
|
|
||||||
if err = z.Close(); err != nil { |
|
||||||
os.Remove(fileName) |
|
||||||
log.Fatalf("Fail to save %s: %v", fileName, err) |
|
||||||
} |
|
||||||
|
|
||||||
log.Printf("Removing tmp work dir: %s", TmpWorkDir) |
|
||||||
os.RemoveAll(TmpWorkDir) |
|
||||||
log.Printf("Finish dumping in file %s", fileName) |
|
||||||
} |
|
@ -0,0 +1,262 @@ |
|||||||
|
// 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 cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"bytes" |
||||||
|
"crypto/tls" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"path" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/urfave/cli" |
||||||
|
log "gopkg.in/clog.v1" |
||||||
|
|
||||||
|
"github.com/gogits/git-module" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/models" |
||||||
|
"github.com/gogits/gogs/pkg/httplib" |
||||||
|
"github.com/gogits/gogs/pkg/mailer" |
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
"github.com/gogits/gogs/pkg/template" |
||||||
|
http "github.com/gogits/gogs/routes/repo" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
Hook = cli.Command{ |
||||||
|
Name: "hook", |
||||||
|
Usage: "Delegate commands to corresponding Git hooks", |
||||||
|
Description: "All sub-commands should only be called by Git", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
subcmdHookPreReceive, |
||||||
|
subcmdHookUpadte, |
||||||
|
subcmdHookPostReceive, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdHookPreReceive = cli.Command{ |
||||||
|
Name: "pre-receive", |
||||||
|
Usage: "Delegate pre-receive Git hook", |
||||||
|
Description: "This command should only be called by Git", |
||||||
|
Action: runHookPreReceive, |
||||||
|
} |
||||||
|
subcmdHookUpadte = cli.Command{ |
||||||
|
Name: "update", |
||||||
|
Usage: "Delegate update Git hook", |
||||||
|
Description: "This command should only be called by Git", |
||||||
|
Action: runHookUpdate, |
||||||
|
} |
||||||
|
subcmdHookPostReceive = cli.Command{ |
||||||
|
Name: "post-receive", |
||||||
|
Usage: "Delegate post-receive Git hook", |
||||||
|
Description: "This command should only be called by Git", |
||||||
|
Action: runHookPostReceive, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func runHookPreReceive(c *cli.Context) error { |
||||||
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
setup(c, "hooks/pre-receive.log", true) |
||||||
|
|
||||||
|
isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/") |
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil) |
||||||
|
scanner := bufio.NewScanner(os.Stdin) |
||||||
|
for scanner.Scan() { |
||||||
|
buf.Write(scanner.Bytes()) |
||||||
|
buf.WriteByte('\n') |
||||||
|
|
||||||
|
if isWiki { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
fields := bytes.Fields(scanner.Bytes()) |
||||||
|
if len(fields) != 3 { |
||||||
|
continue |
||||||
|
} |
||||||
|
oldCommitID := string(fields[0]) |
||||||
|
newCommitID := string(fields[1]) |
||||||
|
branchName := strings.TrimPrefix(string(fields[2]), git.BRANCH_PREFIX) |
||||||
|
|
||||||
|
// Branch protection
|
||||||
|
repoID := com.StrTo(os.Getenv(http.ENV_REPO_ID)).MustInt64() |
||||||
|
protectBranch, err := models.GetProtectBranchOfRepoByName(repoID, branchName) |
||||||
|
if err != nil { |
||||||
|
if models.IsErrBranchNotExist(err) { |
||||||
|
continue |
||||||
|
} |
||||||
|
fail("Internal error", "GetProtectBranchOfRepoByName [repo_id: %d, branch: %s]: %v", repoID, branchName, err) |
||||||
|
} |
||||||
|
if !protectBranch.Protected { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Whitelist users can bypass require pull request check
|
||||||
|
bypassRequirePullRequest := false |
||||||
|
|
||||||
|
// Check if user is in whitelist when enabled
|
||||||
|
userID := com.StrTo(os.Getenv(http.ENV_AUTH_USER_ID)).MustInt64() |
||||||
|
if protectBranch.EnableWhitelist { |
||||||
|
if !models.IsUserInProtectBranchWhitelist(repoID, userID, branchName) { |
||||||
|
fail(fmt.Sprintf("Branch '%s' is protected and you are not in the push whitelist", branchName), "") |
||||||
|
} |
||||||
|
|
||||||
|
bypassRequirePullRequest = true |
||||||
|
} |
||||||
|
|
||||||
|
// Check if branch allows direct push
|
||||||
|
if !bypassRequirePullRequest && protectBranch.RequirePullRequest { |
||||||
|
fail(fmt.Sprintf("Branch '%s' is protected and commits must be merged through pull request", branchName), "") |
||||||
|
} |
||||||
|
|
||||||
|
// check and deletion
|
||||||
|
if newCommitID == git.EMPTY_SHA { |
||||||
|
fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "") |
||||||
|
} |
||||||
|
|
||||||
|
// Check force push
|
||||||
|
output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID). |
||||||
|
RunInDir(models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.ENV_REPO_NAME))) |
||||||
|
if err != nil { |
||||||
|
fail("Internal error", "Fail to detect force push: %v", err) |
||||||
|
} else if len(output) > 0 { |
||||||
|
fail(fmt.Sprintf("Branch '%s' is protected from force push", branchName), "") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "pre-receive") |
||||||
|
if !com.IsFile(customHooksPath) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
hookCmd := exec.Command(customHooksPath) |
||||||
|
hookCmd.Dir = models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.ENV_REPO_NAME)) |
||||||
|
hookCmd.Stdout = os.Stdout |
||||||
|
hookCmd.Stdin = buf |
||||||
|
hookCmd.Stderr = os.Stderr |
||||||
|
if err := hookCmd.Run(); err != nil { |
||||||
|
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func runHookUpdate(c *cli.Context) error { |
||||||
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
setup(c, "hooks/update.log", false) |
||||||
|
|
||||||
|
args := c.Args() |
||||||
|
if len(args) != 3 { |
||||||
|
fail("Arguments received are not equal to three", "Arguments received are not equal to three") |
||||||
|
} else if len(args[0]) == 0 { |
||||||
|
fail("First argument 'refName' is empty", "First argument 'refName' is empty") |
||||||
|
} |
||||||
|
|
||||||
|
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "update") |
||||||
|
if !com.IsFile(customHooksPath) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
hookCmd := exec.Command(customHooksPath, args...) |
||||||
|
hookCmd.Dir = models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.ENV_REPO_NAME)) |
||||||
|
hookCmd.Stdout = os.Stdout |
||||||
|
hookCmd.Stdin = os.Stdin |
||||||
|
hookCmd.Stderr = os.Stderr |
||||||
|
if err := hookCmd.Run(); err != nil { |
||||||
|
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func runHookPostReceive(c *cli.Context) error { |
||||||
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
setup(c, "hooks/post-receive.log", true) |
||||||
|
|
||||||
|
// Post-receive hook does more than just gather Git information,
|
||||||
|
// so we need to setup additional services for email notifications.
|
||||||
|
setting.NewPostReceiveHookServices() |
||||||
|
mailer.NewContext() |
||||||
|
mailer.InitMailRender(path.Join(setting.StaticRootPath, "templates/mail"), |
||||||
|
path.Join(setting.CustomPath, "templates/mail"), template.NewFuncMap()) |
||||||
|
|
||||||
|
isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/") |
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil) |
||||||
|
scanner := bufio.NewScanner(os.Stdin) |
||||||
|
for scanner.Scan() { |
||||||
|
buf.Write(scanner.Bytes()) |
||||||
|
buf.WriteByte('\n') |
||||||
|
|
||||||
|
// TODO: support news feeds for wiki
|
||||||
|
if isWiki { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
fields := bytes.Fields(scanner.Bytes()) |
||||||
|
if len(fields) != 3 { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
options := models.PushUpdateOptions{ |
||||||
|
OldCommitID: string(fields[0]), |
||||||
|
NewCommitID: string(fields[1]), |
||||||
|
RefFullName: string(fields[2]), |
||||||
|
PusherID: com.StrTo(os.Getenv(http.ENV_AUTH_USER_ID)).MustInt64(), |
||||||
|
PusherName: os.Getenv(http.ENV_AUTH_USER_NAME), |
||||||
|
RepoUserName: os.Getenv(http.ENV_REPO_OWNER_NAME), |
||||||
|
RepoName: os.Getenv(http.ENV_REPO_NAME), |
||||||
|
} |
||||||
|
if err := models.PushUpdate(options); err != nil { |
||||||
|
log.Error(2, "PushUpdate: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Ask for running deliver hook and test pull request tasks.
|
||||||
|
reqURL := setting.LocalURL + options.RepoUserName + "/" + options.RepoName + "/tasks/trigger?branch=" + |
||||||
|
strings.TrimPrefix(options.RefFullName, git.BRANCH_PREFIX) + |
||||||
|
"&secret=" + os.Getenv(http.ENV_REPO_OWNER_SALT_MD5) + |
||||||
|
"&pusher=" + os.Getenv(http.ENV_AUTH_USER_ID) |
||||||
|
log.Trace("Trigger task: %s", reqURL) |
||||||
|
|
||||||
|
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{ |
||||||
|
InsecureSkipVerify: true, |
||||||
|
}).Response() |
||||||
|
if err == nil { |
||||||
|
resp.Body.Close() |
||||||
|
if resp.StatusCode/100 != 2 { |
||||||
|
log.Error(2, "Fail to trigger task: not 2xx response code") |
||||||
|
} |
||||||
|
} else { |
||||||
|
log.Error(2, "Fail to trigger task: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "post-receive") |
||||||
|
if !com.IsFile(customHooksPath) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
hookCmd := exec.Command(customHooksPath) |
||||||
|
hookCmd.Dir = models.RepoPath(os.Getenv(http.ENV_REPO_OWNER_NAME), os.Getenv(http.ENV_REPO_NAME)) |
||||||
|
hookCmd.Stdout = os.Stdout |
||||||
|
hookCmd.Stdin = buf |
||||||
|
hookCmd.Stderr = os.Stderr |
||||||
|
if err := hookCmd.Run(); err != nil { |
||||||
|
fail("Internal error", "Fail to execute custom post-receive hook: %v", err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
// Copyright 2016 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 cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/urfave/cli" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
Import = cli.Command{ |
||||||
|
Name: "import", |
||||||
|
Usage: "Import portable data as local Gogs data", |
||||||
|
Description: `Allow user import data from other Gogs installations to local instance |
||||||
|
without manually hacking the data files`, |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
subcmdImportLocale, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
subcmdImportLocale = cli.Command{ |
||||||
|
Name: "locale", |
||||||
|
Usage: "Import locale files to local repository", |
||||||
|
Action: runImportLocale, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("source", "", "Source directory that stores new locale files"), |
||||||
|
stringFlag("target", "", "Target directory that stores old locale files"), |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func runImportLocale(c *cli.Context) error { |
||||||
|
if !c.IsSet("source") { |
||||||
|
return fmt.Errorf("Source directory is not specified") |
||||||
|
} else if !c.IsSet("target") { |
||||||
|
return fmt.Errorf("Target directory is not specified") |
||||||
|
} |
||||||
|
if !com.IsDir(c.String("source")) { |
||||||
|
return fmt.Errorf("Source directory does not exist or is not a directory") |
||||||
|
} else if !com.IsDir(c.String("target")) { |
||||||
|
return fmt.Errorf("Target directory does not exist or is not a directory") |
||||||
|
} |
||||||
|
|
||||||
|
if c.IsSet("config") { |
||||||
|
setting.CustomConf = c.String("config") |
||||||
|
} |
||||||
|
|
||||||
|
setting.NewContext() |
||||||
|
|
||||||
|
now := time.Now() |
||||||
|
|
||||||
|
line := make([]byte, 0, 100) |
||||||
|
badChars := []byte(`="`) |
||||||
|
escapedQuotes := []byte(`\"`) |
||||||
|
regularQuotes := []byte(`"`) |
||||||
|
// Cut out en-US.
|
||||||
|
for _, lang := range setting.Langs[1:] { |
||||||
|
name := fmt.Sprintf("locale_%s.ini", lang) |
||||||
|
source := filepath.Join(c.String("source"), name) |
||||||
|
target := filepath.Join(c.String("target"), name) |
||||||
|
if !com.IsFile(source) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Crowdin surrounds double quotes for strings contain quotes inside,
|
||||||
|
// this breaks INI parser, we need to fix that.
|
||||||
|
sr, err := os.Open(source) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("Open: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
tw, err := os.Create(target) |
||||||
|
if err != nil { |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("Open: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
scanner := bufio.NewScanner(sr) |
||||||
|
for scanner.Scan() { |
||||||
|
line = scanner.Bytes() |
||||||
|
idx := bytes.Index(line, badChars) |
||||||
|
if idx > -1 && line[len(line)-1] == '"' { |
||||||
|
// We still want the "=" sign
|
||||||
|
line = append(line[:idx+1], line[idx+2:len(line)-1]...) |
||||||
|
line = bytes.Replace(line, escapedQuotes, regularQuotes, -1) |
||||||
|
} |
||||||
|
tw.Write(line) |
||||||
|
tw.WriteString("\n") |
||||||
|
} |
||||||
|
sr.Close() |
||||||
|
tw.Close() |
||||||
|
|
||||||
|
// Modification time of files from Crowdin often ahead of current,
|
||||||
|
// so we need to set back to current.
|
||||||
|
os.Chtimes(target, now, now) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("Locale files has been successfully imported!") |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
// Copyright 2017 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 cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
|
||||||
|
"github.com/Unknwon/cae/zip" |
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/mcuadros/go-version" |
||||||
|
"github.com/urfave/cli" |
||||||
|
log "gopkg.in/clog.v1" |
||||||
|
"gopkg.in/ini.v1" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/models" |
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
var Restore = cli.Command{ |
||||||
|
Name: "restore", |
||||||
|
Usage: "Restore files and database from backup", |
||||||
|
Description: `Restore imports all related files and database from a backup archive. |
||||||
|
The backup version must lower or equal to current Gogs version. You can also import |
||||||
|
backup from other database engines, which is useful for database migrating. |
||||||
|
|
||||||
|
If corresponding files or database tables are not presented in the archive, they will |
||||||
|
be skipped and remain unchanged.`, |
||||||
|
Action: runRestore, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
boolFlag("verbose, v", "Show process details"), |
||||||
|
stringFlag("tempdir, t", os.TempDir(), "Temporary directory path"), |
||||||
|
stringFlag("from", "", "Path to backup archive"), |
||||||
|
boolFlag("database-only", "Only import database"), |
||||||
|
boolFlag("exclude-repos", "Exclude repositories"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func runRestore(c *cli.Context) error { |
||||||
|
zip.Verbose = c.Bool("verbose") |
||||||
|
|
||||||
|
tmpDir := c.String("tempdir") |
||||||
|
if !com.IsExist(tmpDir) { |
||||||
|
log.Fatal(0, "'--tempdir' does not exist: %s", tmpDir) |
||||||
|
} |
||||||
|
|
||||||
|
log.Info("Restore backup from: %s", c.String("from")) |
||||||
|
if err := zip.ExtractTo(c.String("from"), tmpDir); err != nil { |
||||||
|
log.Fatal(0, "Fail to extract backup archive: %v", err) |
||||||
|
} |
||||||
|
archivePath := path.Join(tmpDir, _ARCHIVE_ROOT_DIR) |
||||||
|
|
||||||
|
// Check backup version
|
||||||
|
metaFile := path.Join(archivePath, "metadata.ini") |
||||||
|
if !com.IsExist(metaFile) { |
||||||
|
log.Fatal(0, "File 'metadata.ini' is missing") |
||||||
|
} |
||||||
|
metadata, err := ini.Load(metaFile) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(0, "Fail to load metadata '%s': %v", metaFile, err) |
||||||
|
} |
||||||
|
backupVersion := metadata.Section("").Key("GOGS_VERSION").MustString("999.0") |
||||||
|
if version.Compare(setting.AppVer, backupVersion, "<") { |
||||||
|
log.Fatal(0, "Current Gogs version is lower than backup version: %s < %s", setting.AppVer, backupVersion) |
||||||
|
} |
||||||
|
|
||||||
|
// If config file is not present in backup, user must set this file via flag.
|
||||||
|
// Otherwise, it's optional to set config file flag.
|
||||||
|
configFile := path.Join(archivePath, "custom/conf/app.ini") |
||||||
|
if c.IsSet("config") { |
||||||
|
setting.CustomConf = c.String("config") |
||||||
|
} else if !com.IsExist(configFile) { |
||||||
|
log.Fatal(0, "'--config' is not specified and custom config file is not found in backup") |
||||||
|
} else { |
||||||
|
setting.CustomConf = configFile |
||||||
|
} |
||||||
|
setting.NewContext() |
||||||
|
models.LoadConfigs() |
||||||
|
models.SetEngine() |
||||||
|
|
||||||
|
// Database
|
||||||
|
dbDir := path.Join(archivePath, "db") |
||||||
|
if err = models.ImportDatabase(dbDir, c.Bool("verbose")); err != nil { |
||||||
|
log.Fatal(0, "Fail to import database: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Custom files
|
||||||
|
if !c.Bool("database-only") { |
||||||
|
if com.IsExist(setting.CustomPath) { |
||||||
|
if err = os.Rename(setting.CustomPath, setting.CustomPath+".bak"); err != nil { |
||||||
|
log.Fatal(0, "Fail to backup current 'custom': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
if err = os.Rename(path.Join(archivePath, "custom"), setting.CustomPath); err != nil { |
||||||
|
log.Fatal(0, "Fail to import 'custom': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Data files
|
||||||
|
if !c.Bool("database-only") { |
||||||
|
os.MkdirAll(setting.AppDataPath, os.ModePerm) |
||||||
|
for _, dir := range []string{"attachments", "avatars"} { |
||||||
|
// Skip if backup archive does not have corresponding data
|
||||||
|
srcPath := path.Join(archivePath, "data", dir) |
||||||
|
if !com.IsDir(srcPath) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
dirPath := path.Join(setting.AppDataPath, dir) |
||||||
|
if com.IsExist(dirPath) { |
||||||
|
if err = os.Rename(dirPath, dirPath+".bak"); err != nil { |
||||||
|
log.Fatal(0, "Fail to backup current 'data': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
if err = os.Rename(srcPath, dirPath); err != nil { |
||||||
|
log.Fatal(0, "Fail to import 'data': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Repositories
|
||||||
|
reposPath := path.Join(archivePath, "repositories.zip") |
||||||
|
if !c.Bool("exclude-repos") && !c.Bool("database-only") && com.IsExist(reposPath) { |
||||||
|
if err := zip.ExtractTo(reposPath, path.Dir(setting.RepoRootPath)); err != nil { |
||||||
|
log.Fatal(0, "Fail to extract 'repositories.zip': %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
os.RemoveAll(path.Join(tmpDir, _ARCHIVE_ROOT_DIR)) |
||||||
|
log.Info("Restore succeed!") |
||||||
|
log.Shutdown() |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,273 @@ |
|||||||
|
// 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 cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/urfave/cli" |
||||||
|
log "gopkg.in/clog.v1" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/models" |
||||||
|
"github.com/gogits/gogs/models/errors" |
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
http "github.com/gogits/gogs/routes/repo" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
_ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access" |
||||||
|
) |
||||||
|
|
||||||
|
var Serv = cli.Command{ |
||||||
|
Name: "serv", |
||||||
|
Usage: "This command should only be called by SSH shell", |
||||||
|
Description: `Serv provide access auth for repositories`, |
||||||
|
Action: runServ, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func fail(userMessage, logMessage string, args ...interface{}) { |
||||||
|
fmt.Fprintln(os.Stderr, "Gogs:", userMessage) |
||||||
|
|
||||||
|
if len(logMessage) > 0 { |
||||||
|
if !setting.ProdMode { |
||||||
|
fmt.Fprintf(os.Stderr, logMessage+"\n", args...) |
||||||
|
} |
||||||
|
log.Fatal(3, logMessage, args...) |
||||||
|
} |
||||||
|
|
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
func setup(c *cli.Context, logPath string, connectDB bool) { |
||||||
|
if c.IsSet("config") { |
||||||
|
setting.CustomConf = c.String("config") |
||||||
|
} else if c.GlobalIsSet("config") { |
||||||
|
setting.CustomConf = c.GlobalString("config") |
||||||
|
} |
||||||
|
|
||||||
|
setting.NewContext() |
||||||
|
|
||||||
|
level := log.TRACE |
||||||
|
if setting.ProdMode { |
||||||
|
level = log.ERROR |
||||||
|
} |
||||||
|
log.New(log.FILE, log.FileConfig{ |
||||||
|
Level: level, |
||||||
|
Filename: filepath.Join(setting.LogRootPath, logPath), |
||||||
|
FileRotationConfig: log.FileRotationConfig{ |
||||||
|
Rotate: true, |
||||||
|
Daily: true, |
||||||
|
MaxDays: 3, |
||||||
|
}, |
||||||
|
}) |
||||||
|
log.Delete(log.CONSOLE) // Remove primary logger
|
||||||
|
|
||||||
|
if !connectDB { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
models.LoadConfigs() |
||||||
|
|
||||||
|
if setting.UseSQLite3 { |
||||||
|
workDir, _ := setting.WorkDir() |
||||||
|
os.Chdir(workDir) |
||||||
|
} |
||||||
|
|
||||||
|
if err := models.SetEngine(); err != nil { |
||||||
|
fail("Internal error", "SetEngine: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func parseSSHCmd(cmd string) (string, string) { |
||||||
|
ss := strings.SplitN(cmd, " ", 2) |
||||||
|
if len(ss) != 2 { |
||||||
|
return "", "" |
||||||
|
} |
||||||
|
return ss[0], strings.Replace(ss[1], "'/", "'", 1) |
||||||
|
} |
||||||
|
|
||||||
|
func checkDeployKey(key *models.PublicKey, repo *models.Repository) { |
||||||
|
// Check if this deploy key belongs to current repository.
|
||||||
|
if !models.HasDeployKey(key.ID, repo.ID) { |
||||||
|
fail("Key access denied", "Deploy key access denied: [key_id: %d, repo_id: %d]", key.ID, repo.ID) |
||||||
|
} |
||||||
|
|
||||||
|
// Update deploy key activity.
|
||||||
|
deployKey, err := models.GetDeployKeyByRepo(key.ID, repo.ID) |
||||||
|
if err != nil { |
||||||
|
fail("Internal error", "GetDeployKey: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
deployKey.Updated = time.Now() |
||||||
|
if err = models.UpdateDeployKey(deployKey); err != nil { |
||||||
|
fail("Internal error", "UpdateDeployKey: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
allowedCommands = map[string]models.AccessMode{ |
||||||
|
"git-upload-pack": models.ACCESS_MODE_READ, |
||||||
|
"git-upload-archive": models.ACCESS_MODE_READ, |
||||||
|
"git-receive-pack": models.ACCESS_MODE_WRITE, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func runServ(c *cli.Context) error { |
||||||
|
setup(c, "serv.log", true) |
||||||
|
|
||||||
|
if setting.SSH.Disabled { |
||||||
|
println("Gogs: SSH has been disabled") |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if len(c.Args()) < 1 { |
||||||
|
fail("Not enough arguments", "Not enough arguments") |
||||||
|
} |
||||||
|
|
||||||
|
sshCmd := os.Getenv("SSH_ORIGINAL_COMMAND") |
||||||
|
if len(sshCmd) == 0 { |
||||||
|
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.") |
||||||
|
println("If this is unexpected, please log in with password and setup Gogs under another user.") |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
verb, args := parseSSHCmd(sshCmd) |
||||||
|
repoFullName := strings.ToLower(strings.Trim(args, "'")) |
||||||
|
repoFields := strings.SplitN(repoFullName, "/", 2) |
||||||
|
if len(repoFields) != 2 { |
||||||
|
fail("Invalid repository path", "Invalid repository path: %v", args) |
||||||
|
} |
||||||
|
ownerName := strings.ToLower(repoFields[0]) |
||||||
|
repoName := strings.TrimSuffix(strings.ToLower(repoFields[1]), ".git") |
||||||
|
repoName = strings.TrimSuffix(repoName, ".wiki") |
||||||
|
|
||||||
|
owner, err := models.GetUserByName(ownerName) |
||||||
|
if err != nil { |
||||||
|
if errors.IsUserNotExist(err) { |
||||||
|
fail("Repository owner does not exist", "Unregistered owner: %s", ownerName) |
||||||
|
} |
||||||
|
fail("Internal error", "Fail to get repository owner '%s': %v", ownerName, err) |
||||||
|
} |
||||||
|
|
||||||
|
repo, err := models.GetRepositoryByName(owner.ID, repoName) |
||||||
|
if err != nil { |
||||||
|
if errors.IsRepoNotExist(err) { |
||||||
|
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", owner.Name, repoName) |
||||||
|
} |
||||||
|
fail("Internal error", "Fail to get repository: %v", err) |
||||||
|
} |
||||||
|
repo.Owner = owner |
||||||
|
|
||||||
|
requestMode, ok := allowedCommands[verb] |
||||||
|
if !ok { |
||||||
|
fail("Unknown git command", "Unknown git command '%s'", verb) |
||||||
|
} |
||||||
|
|
||||||
|
// Prohibit push to mirror repositories.
|
||||||
|
if requestMode > models.ACCESS_MODE_READ && repo.IsMirror { |
||||||
|
fail("Mirror repository is read-only", "") |
||||||
|
} |
||||||
|
|
||||||
|
// Allow anonymous (user is nil) clone for public repositories.
|
||||||
|
var user *models.User |
||||||
|
|
||||||
|
key, err := models.GetPublicKeyByID(com.StrTo(strings.TrimPrefix(c.Args()[0], "key-")).MustInt64()) |
||||||
|
if err != nil { |
||||||
|
fail("Invalid key ID", "Invalid key ID '%s': %v", c.Args()[0], err) |
||||||
|
} |
||||||
|
|
||||||
|
if requestMode == models.ACCESS_MODE_WRITE || repo.IsPrivate { |
||||||
|
// Check deploy key or user key.
|
||||||
|
if key.IsDeployKey() { |
||||||
|
if key.Mode < requestMode { |
||||||
|
fail("Key permission denied", "Cannot push with deployment key: %d", key.ID) |
||||||
|
} |
||||||
|
checkDeployKey(key, repo) |
||||||
|
} else { |
||||||
|
user, err = models.GetUserByKeyID(key.ID) |
||||||
|
if err != nil { |
||||||
|
fail("Internal error", "Fail to get user by key ID '%d': %v", key.ID, err) |
||||||
|
} |
||||||
|
|
||||||
|
mode, err := models.AccessLevel(user.ID, repo) |
||||||
|
if err != nil { |
||||||
|
fail("Internal error", "Fail to check access: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
if mode < requestMode { |
||||||
|
clientMessage := _ACCESS_DENIED_MESSAGE |
||||||
|
if mode >= models.ACCESS_MODE_READ { |
||||||
|
clientMessage = "You do not have sufficient authorization for this action" |
||||||
|
} |
||||||
|
fail(clientMessage, |
||||||
|
"User '%s' does not have level '%v' access to repository '%s'", |
||||||
|
user.Name, requestMode, repoFullName) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
setting.NewService() |
||||||
|
// Check if the key can access to the repository in case of it is a deploy key (a deploy keys != user key).
|
||||||
|
// A deploy key doesn't represent a signed in user, so in a site with Service.RequireSignInView activated
|
||||||
|
// we should give read access only in repositories where this deploy key is in use. In other case, a server
|
||||||
|
// or system using an active deploy key can get read access to all the repositories in a Gogs service.
|
||||||
|
if key.IsDeployKey() && setting.Service.RequireSignInView { |
||||||
|
checkDeployKey(key, repo) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Update user key activity.
|
||||||
|
if key.ID > 0 { |
||||||
|
key, err := models.GetPublicKeyByID(key.ID) |
||||||
|
if err != nil { |
||||||
|
fail("Internal error", "GetPublicKeyByID: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
key.Updated = time.Now() |
||||||
|
if err = models.UpdatePublicKey(key); err != nil { |
||||||
|
fail("Internal error", "UpdatePublicKey: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Special handle for Windows.
|
||||||
|
if setting.IsWindows { |
||||||
|
verb = strings.Replace(verb, "-", " ", 1) |
||||||
|
} |
||||||
|
|
||||||
|
var gitCmd *exec.Cmd |
||||||
|
verbs := strings.Split(verb, " ") |
||||||
|
if len(verbs) == 2 { |
||||||
|
gitCmd = exec.Command(verbs[0], verbs[1], repoFullName) |
||||||
|
} else { |
||||||
|
gitCmd = exec.Command(verb, repoFullName) |
||||||
|
} |
||||||
|
if requestMode == models.ACCESS_MODE_WRITE { |
||||||
|
gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(http.ComposeHookEnvsOptions{ |
||||||
|
AuthUser: user, |
||||||
|
OwnerName: owner.Name, |
||||||
|
OwnerSalt: owner.Salt, |
||||||
|
RepoID: repo.ID, |
||||||
|
RepoName: repo.Name, |
||||||
|
RepoPath: repo.RepoPath(), |
||||||
|
})...) |
||||||
|
} |
||||||
|
gitCmd.Dir = setting.RepoRootPath |
||||||
|
gitCmd.Stdout = os.Stdout |
||||||
|
gitCmd.Stdin = os.Stdin |
||||||
|
gitCmd.Stderr = os.Stderr |
||||||
|
if err = gitCmd.Run(); err != nil { |
||||||
|
fail("Internal error", "Fail to execute git command: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -1,293 +0,0 @@ |
|||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd |
|
||||||
|
|
||||||
import ( |
|
||||||
"crypto/tls" |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/Unknwon/com" |
|
||||||
"github.com/codegangsta/cli" |
|
||||||
gouuid "github.com/satori/go.uuid" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/models" |
|
||||||
"github.com/gogits/gogs/modules/base" |
|
||||||
"github.com/gogits/gogs/modules/httplib" |
|
||||||
"github.com/gogits/gogs/modules/log" |
|
||||||
"github.com/gogits/gogs/modules/setting" |
|
||||||
) |
|
||||||
|
|
||||||
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", |
|
||||||
Description: `Serv provide access auth for repositories`, |
|
||||||
Action: runServ, |
|
||||||
Flags: []cli.Flag{ |
|
||||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
func setup(logPath string) { |
|
||||||
setting.NewContext() |
|
||||||
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath)) |
|
||||||
|
|
||||||
models.LoadConfigs() |
|
||||||
|
|
||||||
if setting.UseSQLite3 || setting.UseTiDB { |
|
||||||
workDir, _ := setting.WorkDir() |
|
||||||
os.Chdir(workDir) |
|
||||||
} |
|
||||||
|
|
||||||
models.SetEngine() |
|
||||||
} |
|
||||||
|
|
||||||
func parseCmd(cmd string) (string, string) { |
|
||||||
ss := strings.SplitN(cmd, " ", 2) |
|
||||||
if len(ss) != 2 { |
|
||||||
return "", "" |
|
||||||
} |
|
||||||
return ss[0], strings.Replace(ss[1], "'/", "'", 1) |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
allowedCommands = map[string]models.AccessMode{ |
|
||||||
"git-upload-pack": models.ACCESS_MODE_READ, |
|
||||||
"git-upload-archive": models.ACCESS_MODE_READ, |
|
||||||
"git-receive-pack": models.ACCESS_MODE_WRITE, |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
func fail(userMessage, logMessage string, args ...interface{}) { |
|
||||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage) |
|
||||||
|
|
||||||
if len(logMessage) > 0 { |
|
||||||
if !setting.ProdMode { |
|
||||||
fmt.Fprintf(os.Stderr, logMessage+"\n", args...) |
|
||||||
} |
|
||||||
log.GitLogger.Fatal(3, logMessage, args...) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
log.GitLogger.Close() |
|
||||||
os.Exit(1) |
|
||||||
} |
|
||||||
|
|
||||||
func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string, isWiki bool) { |
|
||||||
task, err := models.GetUpdateTaskByUUID(uuid) |
|
||||||
if err != nil { |
|
||||||
if models.IsErrUpdateTaskNotExist(err) { |
|
||||||
log.GitLogger.Trace("No update task is presented: %s", uuid) |
|
||||||
return |
|
||||||
} |
|
||||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err) |
|
||||||
} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil { |
|
||||||
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
if isWiki { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if err = models.PushUpdate(models.PushUpdateOptions{ |
|
||||||
RefName: task.RefName, |
|
||||||
OldCommitID: task.OldCommitID, |
|
||||||
NewCommitID: task.NewCommitID, |
|
||||||
PusherID: user.Id, |
|
||||||
PusherName: user.Name, |
|
||||||
RepoUserName: repoUser.Name, |
|
||||||
RepoName: reponame, |
|
||||||
}); err != nil { |
|
||||||
log.GitLogger.Error(2, "Update: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
// Ask for running deliver hook and test pull request tasks.
|
|
||||||
reqURL := setting.LocalURL + repoUser.Name + "/" + reponame + "/tasks/trigger?branch=" + |
|
||||||
strings.TrimPrefix(task.RefName, "refs/heads/") + "&secret=" + base.EncodeMD5(repoUser.Salt) |
|
||||||
log.GitLogger.Trace("Trigger task: %s", reqURL) |
|
||||||
|
|
||||||
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{ |
|
||||||
InsecureSkipVerify: true, |
|
||||||
}).Response() |
|
||||||
if err == nil { |
|
||||||
resp.Body.Close() |
|
||||||
if resp.StatusCode/100 != 2 { |
|
||||||
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code") |
|
||||||
} |
|
||||||
} else { |
|
||||||
log.GitLogger.Error(2, "Fail to trigger task: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func runServ(c *cli.Context) { |
|
||||||
if c.IsSet("config") { |
|
||||||
setting.CustomConf = c.String("config") |
|
||||||
} |
|
||||||
|
|
||||||
setup("serv.log") |
|
||||||
|
|
||||||
if setting.SSH.Disabled { |
|
||||||
println("Gogs: SSH has been disabled") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if len(c.Args()) < 1 { |
|
||||||
fail("Not enough arguments", "Not enough arguments") |
|
||||||
} |
|
||||||
|
|
||||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") |
|
||||||
if len(cmd) == 0 { |
|
||||||
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.") |
|
||||||
println("If this is unexpected, please log in with password and setup Gogs under another user.") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
verb, args := parseCmd(cmd) |
|
||||||
repoPath := strings.ToLower(strings.Trim(args, "'")) |
|
||||||
rr := strings.SplitN(repoPath, "/", 2) |
|
||||||
if len(rr) != 2 { |
|
||||||
fail("Invalid repository path", "Invalid repository path: %v", args) |
|
||||||
} |
|
||||||
username := strings.ToLower(rr[0]) |
|
||||||
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) |
|
||||||
|
|
||||||
isWiki := false |
|
||||||
if strings.HasSuffix(reponame, ".wiki") { |
|
||||||
isWiki = true |
|
||||||
reponame = reponame[:len(reponame)-5] |
|
||||||
} |
|
||||||
|
|
||||||
repoUser, err := models.GetUserByName(username) |
|
||||||
if err != nil { |
|
||||||
if models.IsErrUserNotExist(err) { |
|
||||||
fail("Repository owner does not exist", "Unregistered owner: %s", username) |
|
||||||
} |
|
||||||
fail("Internal error", "Failed to get repository owner (%s): %v", username, err) |
|
||||||
} |
|
||||||
|
|
||||||
repo, err := models.GetRepositoryByName(repoUser.Id, reponame) |
|
||||||
if err != nil { |
|
||||||
if models.IsErrRepoNotExist(err) { |
|
||||||
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, reponame) |
|
||||||
} |
|
||||||
fail("Internal error", "Failed to get repository: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
requestedMode, has := allowedCommands[verb] |
|
||||||
if !has { |
|
||||||
fail("Unknown git command", "Unknown git command %s", verb) |
|
||||||
} |
|
||||||
|
|
||||||
// Prohibit push to mirror repositories.
|
|
||||||
if requestedMode > models.ACCESS_MODE_READ && repo.IsMirror { |
|
||||||
fail("mirror repository is read-only", "") |
|
||||||
} |
|
||||||
|
|
||||||
// Allow anonymous clone for public repositories.
|
|
||||||
var ( |
|
||||||
keyID int64 |
|
||||||
user *models.User |
|
||||||
) |
|
||||||
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate { |
|
||||||
keys := strings.Split(c.Args()[0], "-") |
|
||||||
if len(keys) != 2 { |
|
||||||
fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) |
|
||||||
} |
|
||||||
|
|
||||||
key, err := models.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64()) |
|
||||||
if err != nil { |
|
||||||
fail("Invalid key ID", "Invalid key ID[%s]: %v", c.Args()[0], err) |
|
||||||
} |
|
||||||
keyID = key.ID |
|
||||||
|
|
||||||
// Check deploy key or user key.
|
|
||||||
if key.Type == models.KEY_TYPE_DEPLOY { |
|
||||||
if key.Mode < requestedMode { |
|
||||||
fail("Key permission denied", "Cannot push with deployment key: %d", key.ID) |
|
||||||
} |
|
||||||
// Check if this deploy key belongs to current repository.
|
|
||||||
if !models.HasDeployKey(key.ID, repo.ID) { |
|
||||||
fail("Key access denied", "Deploy key access denied: [key_id: %d, repo_id: %d]", key.ID, repo.ID) |
|
||||||
} |
|
||||||
|
|
||||||
// Update deploy key activity.
|
|
||||||
deployKey, err := models.GetDeployKeyByRepo(key.ID, repo.ID) |
|
||||||
if err != nil { |
|
||||||
fail("Internal error", "GetDeployKey: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
deployKey.Updated = time.Now() |
|
||||||
if err = models.UpdateDeployKey(deployKey); err != nil { |
|
||||||
fail("Internal error", "UpdateDeployKey: %v", err) |
|
||||||
} |
|
||||||
} else { |
|
||||||
user, err = models.GetUserByKeyID(key.ID) |
|
||||||
if err != nil { |
|
||||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err) |
|
||||||
} |
|
||||||
|
|
||||||
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" |
|
||||||
} |
|
||||||
fail(clientMessage, |
|
||||||
"User %s does not have level %v access to repository %s", |
|
||||||
user.Name, requestedMode, repoPath) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
uuid := gouuid.NewV4().String() |
|
||||||
os.Setenv("uuid", uuid) |
|
||||||
|
|
||||||
// Special handle for Windows.
|
|
||||||
if setting.IsWindows { |
|
||||||
verb = strings.Replace(verb, "-", " ", 1) |
|
||||||
} |
|
||||||
|
|
||||||
var gitcmd *exec.Cmd |
|
||||||
verbs := strings.Split(verb, " ") |
|
||||||
if len(verbs) == 2 { |
|
||||||
gitcmd = exec.Command(verbs[0], verbs[1], repoPath) |
|
||||||
} else { |
|
||||||
gitcmd = exec.Command(verb, repoPath) |
|
||||||
} |
|
||||||
gitcmd.Dir = setting.RepoRootPath |
|
||||||
gitcmd.Stdout = os.Stdout |
|
||||||
gitcmd.Stdin = os.Stdin |
|
||||||
gitcmd.Stderr = os.Stderr |
|
||||||
if err = gitcmd.Run(); err != nil { |
|
||||||
fail("Internal error", "Failed to execute git command: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
if requestedMode == models.ACCESS_MODE_WRITE { |
|
||||||
handleUpdateTask(uuid, user, repoUser, reponame, isWiki) |
|
||||||
} |
|
||||||
|
|
||||||
// Update user key activity.
|
|
||||||
if keyID > 0 { |
|
||||||
key, err := models.GetPublicKeyByID(keyID) |
|
||||||
if err != nil { |
|
||||||
fail("Internal error", "GetPublicKeyById: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
key.Updated = time.Now() |
|
||||||
if err = models.UpdatePublicKey(key); err != nil { |
|
||||||
fail("Internal error", "UpdatePublicKey: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,56 +0,0 @@ |
|||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd |
|
||||||
|
|
||||||
import ( |
|
||||||
"os" |
|
||||||
|
|
||||||
"github.com/codegangsta/cli" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/models" |
|
||||||
"github.com/gogits/gogs/modules/log" |
|
||||||
"github.com/gogits/gogs/modules/setting" |
|
||||||
) |
|
||||||
|
|
||||||
var CmdUpdate = cli.Command{ |
|
||||||
Name: "update", |
|
||||||
Usage: "This command should only be called by Git hook", |
|
||||||
Description: `Update get pushed info and insert into database`, |
|
||||||
Action: runUpdate, |
|
||||||
Flags: []cli.Flag{ |
|
||||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
func runUpdate(c *cli.Context) { |
|
||||||
if c.IsSet("config") { |
|
||||||
setting.CustomConf = c.String("config") |
|
||||||
} |
|
||||||
|
|
||||||
setup("update.log") |
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
|
||||||
log.GitLogger.Trace("SSH_ORIGINAL_COMMAND is empty") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
args := c.Args() |
|
||||||
if len(args) != 3 { |
|
||||||
log.GitLogger.Fatal(2, "Arguments received are not equal to three") |
|
||||||
} else if len(args[0]) == 0 { |
|
||||||
log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use") |
|
||||||
} |
|
||||||
|
|
||||||
task := models.UpdateTask{ |
|
||||||
UUID: os.Getenv("uuid"), |
|
||||||
RefName: args[0], |
|
||||||
OldCommitID: args[1], |
|
||||||
NewCommitID: args[2], |
|
||||||
} |
|
||||||
|
|
||||||
if err := models.AddUpdateTask(&task); err != nil { |
|
||||||
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err) |
|
||||||
} |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
.DS_Store |
|
||||||
.AppleDouble |
|
||||||
.LSOverride |
|
||||||
|
|
||||||
# Icon must end with two \r |
|
||||||
Icon
|
|
||||||
|
|
||||||
# Thumbnails |
|
||||||
._* |
|
||||||
|
|
||||||
# Files that might appear in the root of a volume |
|
||||||
.DocumentRevisions-V100 |
|
||||||
.fseventsd |
|
||||||
.Spotlight-V100 |
|
||||||
.TemporaryItems |
|
||||||
.Trashes |
|
||||||
.VolumeIcon.icns |
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share |
|
||||||
.AppleDB |
|
||||||
.AppleDesktop |
|
||||||
Network Trash Folder |
|
||||||
Temporary Items |
|
||||||
.apdisk |
|
@ -0,0 +1,25 @@ |
|||||||
|
.DS_Store |
||||||
|
.AppleDouble |
||||||
|
.LSOverride |
||||||
|
|
||||||
|
# Icon must end with two \r |
||||||
|
Icon |
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails |
||||||
|
._* |
||||||
|
|
||||||
|
# Files that might appear in the root of a volume |
||||||
|
.DocumentRevisions-V100 |
||||||
|
.fseventsd |
||||||
|
.Spotlight-V100 |
||||||
|
.TemporaryItems |
||||||
|
.Trashes |
||||||
|
.VolumeIcon.icns |
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share |
||||||
|
.AppleDB |
||||||
|
.AppleDesktop |
||||||
|
Network Trash Folder |
||||||
|
Temporary Items |
||||||
|
.apdisk |
@ -0,0 +1,7 @@ |
|||||||
|
#ee0701 bug |
||||||
|
#cccccc duplicate |
||||||
|
#84b6eb enhancement |
||||||
|
#128a0c help wanted |
||||||
|
#e6e6e6 invalid |
||||||
|
#cc317c question |
||||||
|
#ffffff wontfix |
@ -0,0 +1,3 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
go build -ldflags "-w -s" resin-xbuild.go |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,66 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"syscall" |
||||||
|
) |
||||||
|
|
||||||
|
func crossBuildStart() { |
||||||
|
err := os.Remove("/bin/sh") |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
err = os.Link("/usr/bin/resin-xbuild", "/bin/sh") |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func crossBuildEnd() { |
||||||
|
err := os.Remove("/bin/sh") |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
err = os.Link("/bin/sh.real", "/bin/sh") |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func runShell() error { |
||||||
|
cmd := exec.Command("/usr/bin/qemu-arm-static", append([]string{"-0", "/bin/sh", "/bin/sh"}, os.Args[1:]...)...) |
||||||
|
cmd.Stdin = os.Stdin |
||||||
|
cmd.Stdout = os.Stdout |
||||||
|
cmd.Stderr = os.Stderr |
||||||
|
return cmd.Run() |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
switch os.Args[0] { |
||||||
|
case "cross-build-start": |
||||||
|
crossBuildStart() |
||||||
|
case "cross-build-end": |
||||||
|
crossBuildEnd() |
||||||
|
case "/bin/sh": |
||||||
|
code := 0 |
||||||
|
crossBuildEnd() |
||||||
|
|
||||||
|
if err := runShell(); err != nil { |
||||||
|
code = 1 |
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok { |
||||||
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { |
||||||
|
code = status.ExitStatus() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
crossBuildStart() |
||||||
|
|
||||||
|
// Hack to bypass apk issues with triggering
|
||||||
|
code = 0 |
||||||
|
|
||||||
|
os.Exit(code) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
#!/bin/sh |
||||||
|
# Build GO version as specified in Dockerfile |
||||||
|
|
||||||
|
set -x |
||||||
|
set -e |
||||||
|
|
||||||
|
# Components versions |
||||||
|
export GOLANG_VERSION="1.8" |
||||||
|
export GOLANG_SRC_URL="https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz" |
||||||
|
export GOLANG_SRC_SHA256="406865f587b44be7092f206d73fc1de252600b79b3cacc587b74b5ef5c623596" |
||||||
|
|
||||||
|
|
||||||
|
# Install build tools |
||||||
|
apk add --no-cache --no-progress --virtual build-deps-go gcc musl-dev openssl go |
||||||
|
|
||||||
|
export GOROOT_BOOTSTRAP="$(go env GOROOT)" |
||||||
|
|
||||||
|
# Download Go |
||||||
|
wget -q "$GOLANG_SRC_URL" -O golang.tar.gz |
||||||
|
echo "$GOLANG_SRC_SHA256 golang.tar.gz" | sha256sum -c - |
||||||
|
tar -C /usr/local -xzf golang.tar.gz |
||||||
|
rm golang.tar.gz |
||||||
|
|
||||||
|
# Build |
||||||
|
cd /usr/local/go/src |
||||||
|
# see https://golang.org/issue/14851 |
||||||
|
patch -p2 -i /app/gogs/build/docker/no-pic.patch |
||||||
|
./make.bash |
||||||
|
|
||||||
|
# Clean |
||||||
|
rm /app/gogs/build/docker/*.patch |
||||||
|
apk del build-deps-go |
@ -0,0 +1,19 @@ |
|||||||
|
#!/bin/sh |
||||||
|
# Finalize the build |
||||||
|
|
||||||
|
set -x |
||||||
|
set -e |
||||||
|
|
||||||
|
# Move to final place |
||||||
|
mv /app/gogs/build/gogs /app/gogs/ |
||||||
|
|
||||||
|
# Final cleaning |
||||||
|
rm -rf /app/gogs/build |
||||||
|
rm /app/gogs/docker/build.sh |
||||||
|
rm /app/gogs/docker/build-go.sh |
||||||
|
rm /app/gogs/docker/finalize.sh |
||||||
|
rm /app/gogs/docker/nsswitch.conf |
||||||
|
rm /app/gogs/docker/README.md |
||||||
|
|
||||||
|
rm -rf /tmp/go |
||||||
|
rm -rf /usr/local/go |
@ -0,0 +1,16 @@ |
|||||||
|
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
|
||||||
|
index 14f4fa9..5599307 100644
|
||||||
|
--- a/src/cmd/link/internal/ld/lib.go
|
||||||
|
+++ b/src/cmd/link/internal/ld/lib.go
|
||||||
|
@@ -1272,6 +1272,11 @@ func hostlink() {
|
||||||
|
argv = append(argv, peimporteddlls()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // The Go linker does not currently support building PIE
|
||||||
|
+ // executables when using the external linker. See:
|
||||||
|
+ // https://github.com/golang/go/issues/6940
|
||||||
|
+ argv = append(argv, "-fno-PIC")
|
||||||
|
+
|
||||||
|
if l.Debugvlog != 0 {
|
||||||
|
l.Logf("%5.2f host link:", obj.Cputime())
|
||||||
|
for _, v := range argv {
|
@ -1,144 +0,0 @@ |
|||||||
hash: f2fa73b9a379e1fa12f2b48fb0b9942a545b4518a2d71cbd956ee81093347773 |
|
||||||
updated: 2016-03-19T14:44:26.835671547-04:00 |
|
||||||
imports: |
|
||||||
- name: github.com/bradfitz/gomemcache |
|
||||||
version: fb1f79c6b65acda83063cbc69f6bba1522558bfc |
|
||||||
subpackages: |
|
||||||
- memcache |
|
||||||
- name: github.com/codegangsta/cli |
|
||||||
version: aca5b047ed14d17224157c3434ea93bf6cdaadee |
|
||||||
- name: github.com/go-macaron/binding |
|
||||||
version: a68f34212fe257219981e43adfe4c96ab48f42cd |
|
||||||
- name: github.com/go-macaron/cache |
|
||||||
version: 56173531277692bc2925924d51fda1cd0a6b8178 |
|
||||||
subpackages: |
|
||||||
- memcache |
|
||||||
- redis |
|
||||||
- name: github.com/go-macaron/captcha |
|
||||||
version: 8aa5919789ab301e865595eb4b1114d6b9847deb |
|
||||||
- name: github.com/go-macaron/csrf |
|
||||||
version: 6a9a7df172cc1fcd81e4585f44b09200b6087cc0 |
|
||||||
- name: github.com/go-macaron/gzip |
|
||||||
version: cad1c6580a07c56f5f6bc52d66002a05985c5854 |
|
||||||
- name: github.com/go-macaron/i18n |
|
||||||
version: d2d3329f13b52314f3292c4cecb601fad13f02c8 |
|
||||||
- name: github.com/go-macaron/inject |
|
||||||
version: c5ab7bf3a307593cd44cb272d1a5beea473dd072 |
|
||||||
- name: github.com/go-macaron/session |
|
||||||
version: 66031fcb37a0fff002a1f028eb0b3a815c78306b |
|
||||||
subpackages: |
|
||||||
- redis |
|
||||||
- name: github.com/go-macaron/toolbox |
|
||||||
version: 82b511550b0aefc36b3a28062ad3a52e812bee38 |
|
||||||
- name: github.com/go-sql-driver/mysql |
|
||||||
version: 66312f7fe2678aa0f5ec770f96702f4c4ec5aa8e |
|
||||||
- name: github.com/go-xorm/core |
|
||||||
version: 502158401cde814951eae62f064d9e5ff39e13ce |
|
||||||
- name: github.com/go-xorm/xorm |
|
||||||
version: 769f6b3ae663248e8f1b1d8fecbe1eb26ac77ac7 |
|
||||||
- name: github.com/gogits/chardet |
|
||||||
version: 2404f777256163ea3eadb273dada5dcb037993c0 |
|
||||||
- name: github.com/gogits/cron |
|
||||||
version: 3abc0f88f2062336bcc41b43a4febbd847a390ce |
|
||||||
- name: github.com/gogits/git-module |
|
||||||
version: 76e8cce6c7ef3ba1cf75752261c721ebf14cd129 |
|
||||||
- name: github.com/gogits/go-gogs-client |
|
||||||
version: 788ec59749df076b98e208909b44fdef02779deb |
|
||||||
- name: github.com/issue9/identicon |
|
||||||
version: f8c0d2ce04db79c663b1da33d3a9f62be753ee88 |
|
||||||
- name: github.com/kardianos/minwinsvc |
|
||||||
version: cad6b2b879b0970e4245a20ebf1a81a756e2bb70 |
|
||||||
- name: github.com/klauspost/compress |
|
||||||
version: 006acde2c5d283d2f8b8aa03d8f0cd2891c680cf |
|
||||||
subpackages: |
|
||||||
- gzip |
|
||||||
- flate |
|
||||||
- name: github.com/klauspost/cpuid |
|
||||||
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0 |
|
||||||
- name: github.com/klauspost/crc32 |
|
||||||
version: 19b0b332c9e4516a6370a0456e6182c3b5036720 |
|
||||||
- name: github.com/lib/pq |
|
||||||
version: 165a3529e799da61ab10faed1fabff3662d6193f |
|
||||||
subpackages: |
|
||||||
- oid |
|
||||||
- name: github.com/mattn/go-sqlite3 |
|
||||||
version: 76e335f60bbcee20755df9864f0153af1a80ad2d |
|
||||||
- name: github.com/mcuadros/go-version |
|
||||||
version: d52711f8d6bea8dc01efafdb68ad95a4e2606630 |
|
||||||
- name: github.com/microcosm-cc/bluemonday |
|
||||||
version: 4ac6f27528d0a3f2a59e0b0a6f6b3ff0bb89fe20 |
|
||||||
- name: github.com/msteinert/pam |
|
||||||
version: 02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63 |
|
||||||
- name: github.com/nfnt/resize |
|
||||||
version: 4d93a29130b1b6aba503e2aa8b50f516213ea80e |
|
||||||
- name: github.com/russross/blackfriday |
|
||||||
version: b43df972fb5fdf3af8d2e90f38a69d374fe26dd0 |
|
||||||
- name: github.com/satori/go.uuid |
|
||||||
version: e673fdd4dea8a7334adbbe7f57b7e4b00bdc5502 |
|
||||||
- name: github.com/sergi/go-diff |
|
||||||
version: ec7fdbb58eb3e300c8595ad5ac74a5aa50019cc7 |
|
||||||
subpackages: |
|
||||||
- diffmatchpatch |
|
||||||
- name: github.com/shurcooL/sanitized_anchor_name |
|
||||||
version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 |
|
||||||
- name: github.com/Unknwon/cae |
|
||||||
version: 7f5e046bc8a6c3cde743c233b96ee4fd84ee6ecd |
|
||||||
subpackages: |
|
||||||
- zip |
|
||||||
- name: github.com/Unknwon/com |
|
||||||
version: 28b053d5a2923b87ce8c5a08f3af779894a72758 |
|
||||||
- name: github.com/Unknwon/i18n |
|
||||||
version: 3b48b6662051bed72d36efa3c1e897bdf96b2e37 |
|
||||||
- name: github.com/Unknwon/paginater |
|
||||||
version: 7748a72e01415173a27d79866b984328e7b0c12b |
|
||||||
- name: golang.org/x/crypto |
|
||||||
version: c197bcf24cde29d3f73c7b4ac6fd41f4384e8af6 |
|
||||||
subpackages: |
|
||||||
- ssh |
|
||||||
- curve25519 |
|
||||||
- name: golang.org/x/net |
|
||||||
version: 35b06af0720201bc2f326773a80767387544f8c4 |
|
||||||
subpackages: |
|
||||||
- html |
|
||||||
- html/charset |
|
||||||
- html/atom |
|
||||||
- name: golang.org/x/sys |
|
||||||
version: 9d4e42a20653790449273b3c85e67d6d8bae6e2e |
|
||||||
subpackages: |
|
||||||
- windows/svc |
|
||||||
- windows |
|
||||||
- name: golang.org/x/text |
|
||||||
version: 1b466db55e0ba5d56ef5315c728216b42f796491 |
|
||||||
subpackages: |
|
||||||
- transform |
|
||||||
- encoding |
|
||||||
- encoding/charmap |
|
||||||
- encoding/htmlindex |
|
||||||
- encoding/internal/identifier |
|
||||||
- encoding/internal |
|
||||||
- encoding/japanese |
|
||||||
- encoding/korean |
|
||||||
- encoding/simplifiedchinese |
|
||||||
- encoding/traditionalchinese |
|
||||||
- encoding/unicode |
|
||||||
- language |
|
||||||
- internal/utf8internal |
|
||||||
- runes |
|
||||||
- internal/tag |
|
||||||
- name: gopkg.in/alexcesaro/quotedprintable.v3 |
|
||||||
version: 2caba252f4dc53eaf6b553000885530023f54623 |
|
||||||
- name: gopkg.in/asn1-ber.v1 |
|
||||||
version: 4e86f4367175e39f69d9358a5f17b4dda270378d |
|
||||||
- name: gopkg.in/bufio.v1 |
|
||||||
version: 567b2bfa514e796916c4747494d6ff5132a1dfce |
|
||||||
- name: gopkg.in/gomail.v2 |
|
||||||
version: 060a5f4e98dbf37408cf0c745681e4001d877827 |
|
||||||
- name: gopkg.in/ini.v1 |
|
||||||
version: 776aa739ce9373377cd16f526cdf06cb4c89b40f |
|
||||||
- name: gopkg.in/ldap.v2 |
|
||||||
version: 07a7330929b9ee80495c88a4439657d89c7dbd87 |
|
||||||
- name: gopkg.in/macaron.v1 |
|
||||||
version: 94a5ef7105036242f79e5e07a8eb8651d06c8533 |
|
||||||
- name: gopkg.in/redis.v2 |
|
||||||
version: e6179049628164864e6e84e973cfb56335748dea |
|
||||||
devImports: [] |
|
@ -1,56 +0,0 @@ |
|||||||
package: github.com/gogits/gogs |
|
||||||
import: |
|
||||||
- package: github.com/Unknwon/cae |
|
||||||
subpackages: |
|
||||||
- zip |
|
||||||
- package: github.com/Unknwon/com |
|
||||||
- package: github.com/Unknwon/i18n |
|
||||||
- package: github.com/Unknwon/paginater |
|
||||||
- package: github.com/codegangsta/cli |
|
||||||
- package: github.com/go-macaron/binding |
|
||||||
- package: github.com/go-macaron/cache |
|
||||||
subpackages: |
|
||||||
- memcache |
|
||||||
- redis |
|
||||||
- package: github.com/go-macaron/captcha |
|
||||||
- package: github.com/go-macaron/csrf |
|
||||||
- package: github.com/go-macaron/gzip |
|
||||||
- package: github.com/go-macaron/i18n |
|
||||||
- package: github.com/go-macaron/session |
|
||||||
subpackages: |
|
||||||
- redis |
|
||||||
- package: github.com/go-macaron/toolbox |
|
||||||
- package: github.com/go-sql-driver/mysql |
|
||||||
- package: github.com/go-xorm/core |
|
||||||
- package: github.com/go-xorm/xorm |
|
||||||
- package: github.com/gogits/chardet |
|
||||||
- package: github.com/gogits/cron |
|
||||||
- package: github.com/gogits/git-module |
|
||||||
- package: github.com/gogits/go-gogs-client |
|
||||||
- package: github.com/issue9/identicon |
|
||||||
- package: github.com/kardianos/minwinsvc |
|
||||||
- package: github.com/lib/pq |
|
||||||
- package: github.com/mattn/go-sqlite3 |
|
||||||
- package: github.com/mcuadros/go-version |
|
||||||
- package: github.com/microcosm-cc/bluemonday |
|
||||||
- package: github.com/msteinert/pam |
|
||||||
- package: github.com/nfnt/resize |
|
||||||
- package: github.com/russross/blackfriday |
|
||||||
- package: github.com/satori/go.uuid |
|
||||||
- package: github.com/sergi/go-diff |
|
||||||
subpackages: |
|
||||||
- diffmatchpatch |
|
||||||
- package: golang.org/x/crypto |
|
||||||
subpackages: |
|
||||||
- ssh |
|
||||||
- package: golang.org/x/net |
|
||||||
subpackages: |
|
||||||
- html |
|
||||||
- html/charset |
|
||||||
- package: golang.org/x/text |
|
||||||
subpackages: |
|
||||||
- transform |
|
||||||
- package: gopkg.in/gomail.v2 |
|
||||||
- package: gopkg.in/ini.v1 |
|
||||||
- package: gopkg.in/ldap.v2 |
|
||||||
- package: gopkg.in/macaron.v1 |
|
@ -0,0 +1,185 @@ |
|||||||
|
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package models |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"mime/multipart" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/go-xorm/xorm" |
||||||
|
gouuid "github.com/satori/go.uuid" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
// Attachment represent a attachment of issue/comment/release.
|
||||||
|
type Attachment struct { |
||||||
|
ID int64 |
||||||
|
UUID string `xorm:"uuid UNIQUE"` |
||||||
|
IssueID int64 `xorm:"INDEX"` |
||||||
|
CommentID int64 |
||||||
|
ReleaseID int64 `xorm:"INDEX"` |
||||||
|
Name string |
||||||
|
|
||||||
|
Created time.Time `xorm:"-"` |
||||||
|
CreatedUnix int64 |
||||||
|
} |
||||||
|
|
||||||
|
func (a *Attachment) BeforeInsert() { |
||||||
|
a.CreatedUnix = time.Now().Unix() |
||||||
|
} |
||||||
|
|
||||||
|
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { |
||||||
|
switch colName { |
||||||
|
case "created_unix": |
||||||
|
a.Created = time.Unix(a.CreatedUnix, 0).Local() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
|
||||||
|
func AttachmentLocalPath(uuid string) string { |
||||||
|
return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid) |
||||||
|
} |
||||||
|
|
||||||
|
// LocalPath returns where attachment is stored in local file system.
|
||||||
|
func (attach *Attachment) LocalPath() string { |
||||||
|
return AttachmentLocalPath(attach.UUID) |
||||||
|
} |
||||||
|
|
||||||
|
// NewAttachment creates a new attachment object.
|
||||||
|
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) { |
||||||
|
attach := &Attachment{ |
||||||
|
UUID: gouuid.NewV4().String(), |
||||||
|
Name: name, |
||||||
|
} |
||||||
|
|
||||||
|
localPath := attach.LocalPath() |
||||||
|
if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { |
||||||
|
return nil, fmt.Errorf("MkdirAll: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
fw, err := os.Create(localPath) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("Create: %v", err) |
||||||
|
} |
||||||
|
defer fw.Close() |
||||||
|
|
||||||
|
if _, err = fw.Write(buf); err != nil { |
||||||
|
return nil, fmt.Errorf("Write: %v", err) |
||||||
|
} else if _, err = io.Copy(fw, file); err != nil { |
||||||
|
return nil, fmt.Errorf("Copy: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
if _, err := x.Insert(attach); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return attach, nil |
||||||
|
} |
||||||
|
|
||||||
|
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { |
||||||
|
attach := &Attachment{UUID: uuid} |
||||||
|
has, err := x.Get(attach) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} else if !has { |
||||||
|
return nil, ErrAttachmentNotExist{0, uuid} |
||||||
|
} |
||||||
|
return attach, nil |
||||||
|
} |
||||||
|
|
||||||
|
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { |
||||||
|
if len(uuids) == 0 { |
||||||
|
return []*Attachment{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Silently drop invalid uuids.
|
||||||
|
attachments := make([]*Attachment, 0, len(uuids)) |
||||||
|
return attachments, e.In("uuid", uuids).Find(&attachments) |
||||||
|
} |
||||||
|
|
||||||
|
// GetAttachmentByUUID returns attachment by given UUID.
|
||||||
|
func GetAttachmentByUUID(uuid string) (*Attachment, error) { |
||||||
|
return getAttachmentByUUID(x, uuid) |
||||||
|
} |
||||||
|
|
||||||
|
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { |
||||||
|
attachments := make([]*Attachment, 0, 5) |
||||||
|
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) |
||||||
|
} |
||||||
|
|
||||||
|
// GetAttachmentsByIssueID returns all attachments of an issue.
|
||||||
|
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { |
||||||
|
return getAttachmentsByIssueID(x, issueID) |
||||||
|
} |
||||||
|
|
||||||
|
func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) { |
||||||
|
attachments := make([]*Attachment, 0, 5) |
||||||
|
return attachments, e.Where("comment_id=?", commentID).Find(&attachments) |
||||||
|
} |
||||||
|
|
||||||
|
// GetAttachmentsByCommentID returns all attachments of a comment.
|
||||||
|
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { |
||||||
|
return getAttachmentsByCommentID(x, commentID) |
||||||
|
} |
||||||
|
|
||||||
|
func getAttachmentsByReleaseID(e Engine, releaseID int64) ([]*Attachment, error) { |
||||||
|
attachments := make([]*Attachment, 0, 10) |
||||||
|
return attachments, e.Where("release_id = ?", releaseID).Find(&attachments) |
||||||
|
} |
||||||
|
|
||||||
|
// GetAttachmentsByReleaseID returns all attachments of a release.
|
||||||
|
func GetAttachmentsByReleaseID(releaseID int64) ([]*Attachment, error) { |
||||||
|
return getAttachmentsByReleaseID(x, releaseID) |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteAttachment deletes the given attachment and optionally the associated file.
|
||||||
|
func DeleteAttachment(a *Attachment, remove bool) error { |
||||||
|
_, err := DeleteAttachments([]*Attachment{a}, remove) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteAttachments deletes the given attachments and optionally the associated files.
|
||||||
|
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { |
||||||
|
for i, a := range attachments { |
||||||
|
if remove { |
||||||
|
if err := os.Remove(a.LocalPath()); err != nil { |
||||||
|
return i, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if _, err := x.Delete(a); err != nil { |
||||||
|
return i, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return len(attachments), nil |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
|
||||||
|
func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) { |
||||||
|
attachments, err := GetAttachmentsByIssueID(issueId) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
return DeleteAttachments(attachments, remove) |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
|
||||||
|
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) { |
||||||
|
attachments, err := GetAttachmentsByCommentID(commentId) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
return DeleteAttachments(attachments, remove) |
||||||
|
} |
@ -0,0 +1,537 @@ |
|||||||
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package models |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/go-xorm/xorm" |
||||||
|
log "gopkg.in/clog.v1" |
||||||
|
|
||||||
|
api "github.com/gogits/go-gogs-client" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/models/errors" |
||||||
|
"github.com/gogits/gogs/pkg/markup" |
||||||
|
) |
||||||
|
|
||||||
|
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
|
||||||
|
type CommentType int |
||||||
|
|
||||||
|
const ( |
||||||
|
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
|
||||||
|
COMMENT_TYPE_COMMENT CommentType = iota |
||||||
|
COMMENT_TYPE_REOPEN |
||||||
|
COMMENT_TYPE_CLOSE |
||||||
|
|
||||||
|
// References.
|
||||||
|
COMMENT_TYPE_ISSUE_REF |
||||||
|
// Reference from a commit (not part of a pull request)
|
||||||
|
COMMENT_TYPE_COMMIT_REF |
||||||
|
// Reference from a comment
|
||||||
|
COMMENT_TYPE_COMMENT_REF |
||||||
|
// Reference from a pull request
|
||||||
|
COMMENT_TYPE_PULL_REF |
||||||
|
) |
||||||
|
|
||||||
|
type CommentTag int |
||||||
|
|
||||||
|
const ( |
||||||
|
COMMENT_TAG_NONE CommentTag = iota |
||||||
|
COMMENT_TAG_POSTER |
||||||
|
COMMENT_TAG_WRITER |
||||||
|
COMMENT_TAG_OWNER |
||||||
|
) |
||||||
|
|
||||||
|
// Comment represents a comment in commit and issue page.
|
||||||
|
type Comment struct { |
||||||
|
ID int64 |
||||||
|
Type CommentType |
||||||
|
PosterID int64 |
||||||
|
Poster *User `xorm:"-"` |
||||||
|
IssueID int64 `xorm:"INDEX"` |
||||||
|
Issue *Issue `xorm:"-"` |
||||||
|
CommitID int64 |
||||||
|
Line int64 |
||||||
|
Content string `xorm:"TEXT"` |
||||||
|
RenderedContent string `xorm:"-"` |
||||||
|
|
||||||
|
Created time.Time `xorm:"-"` |
||||||
|
CreatedUnix int64 |
||||||
|
Updated time.Time `xorm:"-"` |
||||||
|
UpdatedUnix int64 |
||||||
|
|
||||||
|
// Reference issue in commit message
|
||||||
|
CommitSHA string `xorm:"VARCHAR(40)"` |
||||||
|
|
||||||
|
Attachments []*Attachment `xorm:"-"` |
||||||
|
|
||||||
|
// For view issue page.
|
||||||
|
ShowTag CommentTag `xorm:"-"` |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) BeforeInsert() { |
||||||
|
c.CreatedUnix = time.Now().Unix() |
||||||
|
c.UpdatedUnix = c.CreatedUnix |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) BeforeUpdate() { |
||||||
|
c.UpdatedUnix = time.Now().Unix() |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) AfterSet(colName string, _ xorm.Cell) { |
||||||
|
switch colName { |
||||||
|
case "created_unix": |
||||||
|
c.Created = time.Unix(c.CreatedUnix, 0).Local() |
||||||
|
case "updated_unix": |
||||||
|
c.Updated = time.Unix(c.UpdatedUnix, 0).Local() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) loadAttributes(e Engine) (err error) { |
||||||
|
if c.Poster == nil { |
||||||
|
c.Poster, err = GetUserByID(c.PosterID) |
||||||
|
if err != nil { |
||||||
|
if errors.IsUserNotExist(err) { |
||||||
|
c.PosterID = -1 |
||||||
|
c.Poster = NewGhostUser() |
||||||
|
} else { |
||||||
|
return fmt.Errorf("getUserByID.(Poster) [%d]: %v", c.PosterID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if c.Issue == nil { |
||||||
|
c.Issue, err = getRawIssueByID(e, c.IssueID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("getIssueByID [%d]: %v", c.IssueID, err) |
||||||
|
} |
||||||
|
if c.Issue.Repo == nil { |
||||||
|
c.Issue.Repo, err = getRepositoryByID(e, c.Issue.RepoID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("getRepositoryByID [%d]: %v", c.Issue.RepoID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if c.Attachments == nil { |
||||||
|
c.Attachments, err = getAttachmentsByCommentID(e, c.ID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("getAttachmentsByCommentID [%d]: %v", c.ID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) LoadAttributes() error { |
||||||
|
return c.loadAttributes(x) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) AfterDelete() { |
||||||
|
_, err := DeleteAttachmentsByComment(c.ID, true) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) HTMLURL() string { |
||||||
|
return fmt.Sprintf("%s#issuecomment-%d", c.Issue.HTMLURL(), c.ID) |
||||||
|
} |
||||||
|
|
||||||
|
// This method assumes following fields have been assigned with valid values:
|
||||||
|
// Required - Poster, Issue
|
||||||
|
func (c *Comment) APIFormat() *api.Comment { |
||||||
|
return &api.Comment{ |
||||||
|
ID: c.ID, |
||||||
|
HTMLURL: c.HTMLURL(), |
||||||
|
Poster: c.Poster.APIFormat(), |
||||||
|
Body: c.Content, |
||||||
|
Created: c.Created, |
||||||
|
Updated: c.Updated, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func CommentHashTag(id int64) string { |
||||||
|
return "issuecomment-" + com.ToStr(id) |
||||||
|
} |
||||||
|
|
||||||
|
// HashTag returns unique hash tag for comment.
|
||||||
|
func (c *Comment) HashTag() string { |
||||||
|
return CommentHashTag(c.ID) |
||||||
|
} |
||||||
|
|
||||||
|
// EventTag returns unique event hash tag for comment.
|
||||||
|
func (c *Comment) EventTag() string { |
||||||
|
return "event-" + com.ToStr(c.ID) |
||||||
|
} |
||||||
|
|
||||||
|
// mailParticipants sends new comment emails to repository watchers
|
||||||
|
// and mentioned people.
|
||||||
|
func (cmt *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { |
||||||
|
mentions := markup.FindAllMentions(cmt.Content) |
||||||
|
if err = updateIssueMentions(e, cmt.IssueID, mentions); err != nil { |
||||||
|
return fmt.Errorf("UpdateIssueMentions [%d]: %v", cmt.IssueID, err) |
||||||
|
} |
||||||
|
|
||||||
|
switch opType { |
||||||
|
case ACTION_COMMENT_ISSUE: |
||||||
|
issue.Content = cmt.Content |
||||||
|
case ACTION_CLOSE_ISSUE: |
||||||
|
issue.Content = fmt.Sprintf("Closed #%d", issue.Index) |
||||||
|
case ACTION_REOPEN_ISSUE: |
||||||
|
issue.Content = fmt.Sprintf("Reopened #%d", issue.Index) |
||||||
|
} |
||||||
|
if err = mailIssueCommentToParticipants(issue, cmt.Poster, mentions); err != nil { |
||||||
|
log.Error(2, "mailIssueCommentToParticipants: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { |
||||||
|
comment := &Comment{ |
||||||
|
Type: opts.Type, |
||||||
|
PosterID: opts.Doer.ID, |
||||||
|
Poster: opts.Doer, |
||||||
|
IssueID: opts.Issue.ID, |
||||||
|
CommitID: opts.CommitID, |
||||||
|
CommitSHA: opts.CommitSHA, |
||||||
|
Line: opts.LineNum, |
||||||
|
Content: opts.Content, |
||||||
|
} |
||||||
|
if _, err = e.Insert(comment); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Compose comment action, could be plain comment, close or reopen issue/pull request.
|
||||||
|
// This object will be used to notify watchers in the end of function.
|
||||||
|
act := &Action{ |
||||||
|
ActUserID: opts.Doer.ID, |
||||||
|
ActUserName: opts.Doer.Name, |
||||||
|
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), |
||||||
|
RepoID: opts.Repo.ID, |
||||||
|
RepoUserName: opts.Repo.Owner.Name, |
||||||
|
RepoName: opts.Repo.Name, |
||||||
|
IsPrivate: opts.Repo.IsPrivate, |
||||||
|
} |
||||||
|
|
||||||
|
// Check comment type.
|
||||||
|
switch opts.Type { |
||||||
|
case COMMENT_TYPE_COMMENT: |
||||||
|
act.OpType = ACTION_COMMENT_ISSUE |
||||||
|
|
||||||
|
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Check attachments
|
||||||
|
attachments := make([]*Attachment, 0, len(opts.Attachments)) |
||||||
|
for _, uuid := range opts.Attachments { |
||||||
|
attach, err := getAttachmentByUUID(e, uuid) |
||||||
|
if err != nil { |
||||||
|
if IsErrAttachmentNotExist(err) { |
||||||
|
continue |
||||||
|
} |
||||||
|
return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err) |
||||||
|
} |
||||||
|
attachments = append(attachments, attach) |
||||||
|
} |
||||||
|
|
||||||
|
for i := range attachments { |
||||||
|
attachments[i].IssueID = opts.Issue.ID |
||||||
|
attachments[i].CommentID = comment.ID |
||||||
|
// No assign value could be 0, so ignore AllCols().
|
||||||
|
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil { |
||||||
|
return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case COMMENT_TYPE_REOPEN: |
||||||
|
act.OpType = ACTION_REOPEN_ISSUE |
||||||
|
if opts.Issue.IsPull { |
||||||
|
act.OpType = ACTION_REOPEN_PULL_REQUEST |
||||||
|
} |
||||||
|
|
||||||
|
if opts.Issue.IsPull { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) |
||||||
|
} else { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
case COMMENT_TYPE_CLOSE: |
||||||
|
act.OpType = ACTION_CLOSE_ISSUE |
||||||
|
if opts.Issue.IsPull { |
||||||
|
act.OpType = ACTION_CLOSE_PULL_REQUEST |
||||||
|
} |
||||||
|
|
||||||
|
if opts.Issue.IsPull { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) |
||||||
|
} else { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if _, err = e.Exec("UPDATE `issue` SET updated_unix = ? WHERE id = ?", time.Now().Unix(), opts.Issue.ID); err != nil { |
||||||
|
return nil, fmt.Errorf("update issue 'updated_unix': %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Notify watchers for whatever action comes in, ignore if no action type.
|
||||||
|
if act.OpType > 0 { |
||||||
|
if err = notifyWatchers(e, act); err != nil { |
||||||
|
log.Error(2, "notifyWatchers: %v", err) |
||||||
|
} |
||||||
|
if err = comment.mailParticipants(e, act.OpType, opts.Issue); err != nil { |
||||||
|
log.Error(2, "MailParticipants: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return comment, comment.loadAttributes(e) |
||||||
|
} |
||||||
|
|
||||||
|
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { |
||||||
|
cmtType := COMMENT_TYPE_CLOSE |
||||||
|
if !issue.IsClosed { |
||||||
|
cmtType = COMMENT_TYPE_REOPEN |
||||||
|
} |
||||||
|
return createComment(e, &CreateCommentOptions{ |
||||||
|
Type: cmtType, |
||||||
|
Doer: doer, |
||||||
|
Repo: repo, |
||||||
|
Issue: issue, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
type CreateCommentOptions struct { |
||||||
|
Type CommentType |
||||||
|
Doer *User |
||||||
|
Repo *Repository |
||||||
|
Issue *Issue |
||||||
|
|
||||||
|
CommitID int64 |
||||||
|
CommitSHA string |
||||||
|
LineNum int64 |
||||||
|
Content string |
||||||
|
Attachments []string // UUIDs of attachments
|
||||||
|
} |
||||||
|
|
||||||
|
// CreateComment creates comment of issue or commit.
|
||||||
|
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { |
||||||
|
sess := x.NewSession() |
||||||
|
defer sess.Close() |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
comment, err = createComment(sess, opts) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return comment, sess.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
// CreateIssueComment creates a plain issue comment.
|
||||||
|
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { |
||||||
|
comment, err := CreateComment(&CreateCommentOptions{ |
||||||
|
Type: COMMENT_TYPE_COMMENT, |
||||||
|
Doer: doer, |
||||||
|
Repo: repo, |
||||||
|
Issue: issue, |
||||||
|
Content: content, |
||||||
|
Attachments: attachments, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("CreateComment: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
comment.Issue = issue |
||||||
|
if err = PrepareWebhooks(repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{ |
||||||
|
Action: api.HOOK_ISSUE_COMMENT_CREATED, |
||||||
|
Issue: issue.APIFormat(), |
||||||
|
Comment: comment.APIFormat(), |
||||||
|
Repository: repo.APIFormat(nil), |
||||||
|
Sender: doer.APIFormat(), |
||||||
|
}); err != nil { |
||||||
|
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) |
||||||
|
} |
||||||
|
|
||||||
|
return comment, nil |
||||||
|
} |
||||||
|
|
||||||
|
// CreateRefComment creates a commit reference comment to issue.
|
||||||
|
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { |
||||||
|
if len(commitSHA) == 0 { |
||||||
|
return fmt.Errorf("cannot create reference with empty commit SHA") |
||||||
|
} |
||||||
|
|
||||||
|
// Check if same reference from same commit has already existed.
|
||||||
|
has, err := x.Get(&Comment{ |
||||||
|
Type: COMMENT_TYPE_COMMIT_REF, |
||||||
|
IssueID: issue.ID, |
||||||
|
CommitSHA: commitSHA, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("check reference comment: %v", err) |
||||||
|
} else if has { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
_, err = CreateComment(&CreateCommentOptions{ |
||||||
|
Type: COMMENT_TYPE_COMMIT_REF, |
||||||
|
Doer: doer, |
||||||
|
Repo: repo, |
||||||
|
Issue: issue, |
||||||
|
CommitSHA: commitSHA, |
||||||
|
Content: content, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// GetCommentByID returns the comment by given ID.
|
||||||
|
func GetCommentByID(id int64) (*Comment, error) { |
||||||
|
c := new(Comment) |
||||||
|
has, err := x.Id(id).Get(c) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} else if !has { |
||||||
|
return nil, ErrCommentNotExist{id, 0} |
||||||
|
} |
||||||
|
return c, c.LoadAttributes() |
||||||
|
} |
||||||
|
|
||||||
|
// FIXME: use CommentList to improve performance.
|
||||||
|
func loadCommentsAttributes(e Engine, comments []*Comment) (err error) { |
||||||
|
for i := range comments { |
||||||
|
if err = comments[i].loadAttributes(e); err != nil { |
||||||
|
return fmt.Errorf("loadAttributes [%d]: %v", comments[i].ID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, error) { |
||||||
|
comments := make([]*Comment, 0, 10) |
||||||
|
sess := e.Where("issue_id = ?", issueID).Asc("created_unix") |
||||||
|
if since > 0 { |
||||||
|
sess.And("updated_unix >= ?", since) |
||||||
|
} |
||||||
|
|
||||||
|
if err := sess.Find(&comments); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return comments, loadCommentsAttributes(e, comments) |
||||||
|
} |
||||||
|
|
||||||
|
func getCommentsByRepoIDSince(e Engine, repoID, since int64) ([]*Comment, error) { |
||||||
|
comments := make([]*Comment, 0, 10) |
||||||
|
sess := e.Where("issue.repo_id = ?", repoID).Join("INNER", "issue", "issue.id = comment.issue_id").Asc("comment.created_unix") |
||||||
|
if since > 0 { |
||||||
|
sess.And("comment.updated_unix >= ?", since) |
||||||
|
} |
||||||
|
if err := sess.Find(&comments); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return comments, loadCommentsAttributes(e, comments) |
||||||
|
} |
||||||
|
|
||||||
|
func getCommentsByIssueID(e Engine, issueID int64) ([]*Comment, error) { |
||||||
|
return getCommentsByIssueIDSince(e, issueID, -1) |
||||||
|
} |
||||||
|
|
||||||
|
// GetCommentsByIssueID returns all comments of an issue.
|
||||||
|
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) { |
||||||
|
return getCommentsByIssueID(x, issueID) |
||||||
|
} |
||||||
|
|
||||||
|
// GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point.
|
||||||
|
func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) { |
||||||
|
return getCommentsByIssueIDSince(x, issueID, since) |
||||||
|
} |
||||||
|
|
||||||
|
// GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point.
|
||||||
|
func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) { |
||||||
|
return getCommentsByRepoIDSince(x, repoID, since) |
||||||
|
} |
||||||
|
|
||||||
|
// UpdateComment updates information of comment.
|
||||||
|
func UpdateComment(doer *User, c *Comment, oldContent string) (err error) { |
||||||
|
if _, err = x.Id(c.ID).AllCols().Update(c); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = c.Issue.LoadAttributes(); err != nil { |
||||||
|
log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", c.IssueID, err) |
||||||
|
} else if err = PrepareWebhooks(c.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{ |
||||||
|
Action: api.HOOK_ISSUE_COMMENT_EDITED, |
||||||
|
Issue: c.Issue.APIFormat(), |
||||||
|
Comment: c.APIFormat(), |
||||||
|
Changes: &api.ChangesPayload{ |
||||||
|
Body: &api.ChangesFromPayload{ |
||||||
|
From: oldContent, |
||||||
|
}, |
||||||
|
}, |
||||||
|
Repository: c.Issue.Repo.APIFormat(nil), |
||||||
|
Sender: doer.APIFormat(), |
||||||
|
}); err != nil { |
||||||
|
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteCommentByID deletes the comment by given ID.
|
||||||
|
func DeleteCommentByID(doer *User, id int64) error { |
||||||
|
comment, err := GetCommentByID(id) |
||||||
|
if err != nil { |
||||||
|
if IsErrCommentNotExist(err) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
sess := x.NewSession() |
||||||
|
defer sess.Close() |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if _, err = sess.Id(comment.ID).Delete(new(Comment)); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if comment.Type == COMMENT_TYPE_COMMENT { |
||||||
|
if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if err = sess.Commit(); err != nil { |
||||||
|
return fmt.Errorf("Commit: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
if err = comment.Issue.LoadAttributes(); err != nil { |
||||||
|
log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", comment.IssueID, err) |
||||||
|
} else if err = PrepareWebhooks(comment.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{ |
||||||
|
Action: api.HOOK_ISSUE_COMMENT_DELETED, |
||||||
|
Issue: comment.Issue.APIFormat(), |
||||||
|
Comment: comment.APIFormat(), |
||||||
|
Repository: comment.Issue.Repo.APIFormat(nil), |
||||||
|
Sender: doer.APIFormat(), |
||||||
|
}); err != nil { |
||||||
|
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "errors" |
||||||
|
|
||||||
|
// New is a wrapper of real errors.New function.
|
||||||
|
func New(text string) error { |
||||||
|
return errors.New(text) |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type IssueNotExist struct { |
||||||
|
ID int64 |
||||||
|
RepoID int64 |
||||||
|
Index int64 |
||||||
|
} |
||||||
|
|
||||||
|
func IsIssueNotExist(err error) bool { |
||||||
|
_, ok := err.(IssueNotExist) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err IssueNotExist) Error() string { |
||||||
|
return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index) |
||||||
|
} |
||||||
|
|
||||||
|
type InvalidIssueReference struct { |
||||||
|
Ref string |
||||||
|
} |
||||||
|
|
||||||
|
func IsInvalidIssueReference(err error) bool { |
||||||
|
_, ok := err.(InvalidIssueReference) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err InvalidIssueReference) Error() string { |
||||||
|
return fmt.Sprintf("invalid issue reference [ref: %s]", err.Ref) |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type LoginSourceNotActivated struct { |
||||||
|
SourceID int64 |
||||||
|
} |
||||||
|
|
||||||
|
func IsLoginSourceNotActivated(err error) bool { |
||||||
|
_, ok := err.(LoginSourceNotActivated) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err LoginSourceNotActivated) Error() string { |
||||||
|
return fmt.Sprintf("login source is not activated [source_id: %d]", err.SourceID) |
||||||
|
} |
||||||
|
|
||||||
|
type InvalidLoginSourceType struct { |
||||||
|
Type interface{} |
||||||
|
} |
||||||
|
|
||||||
|
func IsInvalidLoginSourceType(err error) bool { |
||||||
|
_, ok := err.(InvalidLoginSourceType) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err InvalidLoginSourceType) Error() string { |
||||||
|
return fmt.Sprintf("invalid login source type [type: %v]", err.Type) |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type RepoNotExist struct { |
||||||
|
ID int64 |
||||||
|
UserID int64 |
||||||
|
Name string |
||||||
|
} |
||||||
|
|
||||||
|
func IsRepoNotExist(err error) bool { |
||||||
|
_, ok := err.(RepoNotExist) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err RepoNotExist) Error() string { |
||||||
|
return fmt.Sprintf("repository does not exist [id: %d, user_id: %d, name: %s]", err.ID, err.UserID, err.Name) |
||||||
|
} |
||||||
|
|
||||||
|
type ReachLimitOfRepo struct { |
||||||
|
Limit int |
||||||
|
} |
||||||
|
|
||||||
|
func IsReachLimitOfRepo(err error) bool { |
||||||
|
_, ok := err.(ReachLimitOfRepo) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err ReachLimitOfRepo) Error() string { |
||||||
|
return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit) |
||||||
|
} |
||||||
|
|
||||||
|
type InvalidRepoReference struct { |
||||||
|
Ref string |
||||||
|
} |
||||||
|
|
||||||
|
func IsInvalidRepoReference(err error) bool { |
||||||
|
_, ok := err.(InvalidRepoReference) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err InvalidRepoReference) Error() string { |
||||||
|
return fmt.Sprintf("invalid repository reference [ref: %s]", err.Ref) |
||||||
|
} |
||||||
|
|
||||||
|
type MirrorNotExist struct { |
||||||
|
RepoID int64 |
||||||
|
} |
||||||
|
|
||||||
|
func IsMirrorNotExist(err error) bool { |
||||||
|
_, ok := err.(MirrorNotExist) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err MirrorNotExist) Error() string { |
||||||
|
return fmt.Sprintf("mirror does not exist [repo_id: %d]", err.RepoID) |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type TwoFactorNotFound struct { |
||||||
|
UserID int64 |
||||||
|
} |
||||||
|
|
||||||
|
func IsTwoFactorNotFound(err error) bool { |
||||||
|
_, ok := err.(TwoFactorNotFound) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err TwoFactorNotFound) Error() string { |
||||||
|
return fmt.Sprintf("two-factor authentication does not found [user_id: %d]", err.UserID) |
||||||
|
} |
||||||
|
|
||||||
|
type TwoFactorRecoveryCodeNotFound struct { |
||||||
|
Code string |
||||||
|
} |
||||||
|
|
||||||
|
func IsTwoFactorRecoveryCodeNotFound(err error) bool { |
||||||
|
_, ok := err.(TwoFactorRecoveryCodeNotFound) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err TwoFactorRecoveryCodeNotFound) Error() string { |
||||||
|
return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code) |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type EmptyName struct{} |
||||||
|
|
||||||
|
func IsEmptyName(err error) bool { |
||||||
|
_, ok := err.(EmptyName) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err EmptyName) Error() string { |
||||||
|
return "empty name" |
||||||
|
} |
||||||
|
|
||||||
|
type UserNotExist struct { |
||||||
|
UserID int64 |
||||||
|
Name string |
||||||
|
} |
||||||
|
|
||||||
|
func IsUserNotExist(err error) bool { |
||||||
|
_, ok := err.(UserNotExist) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err UserNotExist) Error() string { |
||||||
|
return fmt.Sprintf("user does not exist [user_id: %d, name: %s]", err.UserID, err.Name) |
||||||
|
} |
||||||
|
|
||||||
|
type UserNotKeyOwner struct { |
||||||
|
KeyID int64 |
||||||
|
} |
||||||
|
|
||||||
|
func IsUserNotKeyOwner(err error) bool { |
||||||
|
_, ok := err.(UserNotKeyOwner) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err UserNotKeyOwner) Error() string { |
||||||
|
return fmt.Sprintf("user is not the owner of public key [key_id: %d]", err.KeyID) |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type EmailNotFound struct { |
||||||
|
Email string |
||||||
|
} |
||||||
|
|
||||||
|
func IsEmailNotFound(err error) bool { |
||||||
|
_, ok := err.(EmailNotFound) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err EmailNotFound) Error() string { |
||||||
|
return fmt.Sprintf("email is not found [email: %s]", err.Email) |
||||||
|
} |
||||||
|
|
||||||
|
type EmailNotVerified struct { |
||||||
|
Email string |
||||||
|
} |
||||||
|
|
||||||
|
func IsEmailNotVerified(err error) bool { |
||||||
|
_, ok := err.(EmailNotVerified) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err EmailNotVerified) Error() string { |
||||||
|
return fmt.Sprintf("email has not been verified [email: %s]", err.Email) |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
// Copyright 2017 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 errors |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type WebhookNotExist struct { |
||||||
|
ID int64 |
||||||
|
} |
||||||
|
|
||||||
|
func IsWebhookNotExist(err error) bool { |
||||||
|
_, ok := err.(WebhookNotExist) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err WebhookNotExist) Error() string { |
||||||
|
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID) |
||||||
|
} |
||||||
|
|
||||||
|
type HookTaskNotExist struct { |
||||||
|
HookID int64 |
||||||
|
UUID string |
||||||
|
} |
||||||
|
|
||||||
|
func IsHookTaskNotExist(err error) bool { |
||||||
|
_, ok := err.(HookTaskNotExist) |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
func (err HookTaskNotExist) Error() string { |
||||||
|
return fmt.Sprintf("hook task does not exist [hook_id: %d, uuid: %s]", err.HookID, err.UUID) |
||||||
|
} |
@ -1,320 +0,0 @@ |
|||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package models |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/Unknwon/com" |
|
||||||
"github.com/go-xorm/xorm" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/modules/log" |
|
||||||
) |
|
||||||
|
|
||||||
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
|
|
||||||
type CommentType int |
|
||||||
|
|
||||||
const ( |
|
||||||
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
|
|
||||||
COMMENT_TYPE_COMMENT CommentType = iota |
|
||||||
COMMENT_TYPE_REOPEN |
|
||||||
COMMENT_TYPE_CLOSE |
|
||||||
|
|
||||||
// References.
|
|
||||||
COMMENT_TYPE_ISSUE_REF |
|
||||||
// Reference from a commit (not part of a pull request)
|
|
||||||
COMMENT_TYPE_COMMIT_REF |
|
||||||
// Reference from a comment
|
|
||||||
COMMENT_TYPE_COMMENT_REF |
|
||||||
// Reference from a pull request
|
|
||||||
COMMENT_TYPE_PULL_REF |
|
||||||
) |
|
||||||
|
|
||||||
type CommentTag int |
|
||||||
|
|
||||||
const ( |
|
||||||
COMMENT_TAG_NONE CommentTag = iota |
|
||||||
COMMENT_TAG_POSTER |
|
||||||
COMMENT_TAG_WRITER |
|
||||||
COMMENT_TAG_OWNER |
|
||||||
) |
|
||||||
|
|
||||||
// Comment represents a comment in commit and issue page.
|
|
||||||
type Comment struct { |
|
||||||
ID int64 `xorm:"pk autoincr"` |
|
||||||
Type CommentType |
|
||||||
PosterID int64 |
|
||||||
Poster *User `xorm:"-"` |
|
||||||
IssueID int64 `xorm:"INDEX"` |
|
||||||
CommitID int64 |
|
||||||
Line int64 |
|
||||||
Content string `xorm:"TEXT"` |
|
||||||
RenderedContent string `xorm:"-"` |
|
||||||
|
|
||||||
Created time.Time `xorm:"-"` |
|
||||||
CreatedUnix int64 |
|
||||||
|
|
||||||
// Reference issue in commit message
|
|
||||||
CommitSHA string `xorm:"VARCHAR(40)"` |
|
||||||
|
|
||||||
Attachments []*Attachment `xorm:"-"` |
|
||||||
|
|
||||||
// For view issue page.
|
|
||||||
ShowTag CommentTag `xorm:"-"` |
|
||||||
} |
|
||||||
|
|
||||||
func (c *Comment) BeforeInsert() { |
|
||||||
c.CreatedUnix = time.Now().UTC().Unix() |
|
||||||
} |
|
||||||
|
|
||||||
func (c *Comment) AfterSet(colName string, _ xorm.Cell) { |
|
||||||
var err error |
|
||||||
switch colName { |
|
||||||
case "id": |
|
||||||
c.Attachments, err = GetAttachmentsByCommentID(c.ID) |
|
||||||
if err != nil { |
|
||||||
log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err) |
|
||||||
} |
|
||||||
|
|
||||||
case "poster_id": |
|
||||||
c.Poster, err = GetUserByID(c.PosterID) |
|
||||||
if err != nil { |
|
||||||
if IsErrUserNotExist(err) { |
|
||||||
c.PosterID = -1 |
|
||||||
c.Poster = NewFakeUser() |
|
||||||
} else { |
|
||||||
log.Error(3, "GetUserByID[%d]: %v", c.ID, err) |
|
||||||
} |
|
||||||
} |
|
||||||
case "created_unix": |
|
||||||
c.Created = time.Unix(c.CreatedUnix, 0).Local() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *Comment) AfterDelete() { |
|
||||||
_, err := DeleteAttachmentsByComment(c.ID, true) |
|
||||||
|
|
||||||
if err != nil { |
|
||||||
log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// HashTag returns unique hash tag for comment.
|
|
||||||
func (c *Comment) HashTag() string { |
|
||||||
return "issuecomment-" + com.ToStr(c.ID) |
|
||||||
} |
|
||||||
|
|
||||||
// EventTag returns unique event hash tag for comment.
|
|
||||||
func (c *Comment) EventTag() string { |
|
||||||
return "event-" + com.ToStr(c.ID) |
|
||||||
} |
|
||||||
|
|
||||||
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { |
|
||||||
comment := &Comment{ |
|
||||||
Type: opts.Type, |
|
||||||
PosterID: opts.Doer.Id, |
|
||||||
IssueID: opts.Issue.ID, |
|
||||||
CommitID: opts.CommitID, |
|
||||||
CommitSHA: opts.CommitSHA, |
|
||||||
Line: opts.LineNum, |
|
||||||
Content: opts.Content, |
|
||||||
} |
|
||||||
if _, err = e.Insert(comment); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
// Compose comment action, could be plain comment, close or reopen issue/pull request.
|
|
||||||
// This object will be used to notify watchers in the end of function.
|
|
||||||
act := &Action{ |
|
||||||
ActUserID: opts.Doer.Id, |
|
||||||
ActUserName: opts.Doer.Name, |
|
||||||
ActEmail: opts.Doer.Email, |
|
||||||
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), |
|
||||||
RepoID: opts.Repo.ID, |
|
||||||
RepoUserName: opts.Repo.Owner.Name, |
|
||||||
RepoName: opts.Repo.Name, |
|
||||||
IsPrivate: opts.Repo.IsPrivate, |
|
||||||
} |
|
||||||
|
|
||||||
// Check comment type.
|
|
||||||
switch opts.Type { |
|
||||||
case COMMENT_TYPE_COMMENT: |
|
||||||
act.OpType = ACTION_COMMENT_ISSUE |
|
||||||
|
|
||||||
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
// Check attachments
|
|
||||||
attachments := make([]*Attachment, 0, len(opts.Attachments)) |
|
||||||
for _, uuid := range opts.Attachments { |
|
||||||
attach, err := getAttachmentByUUID(e, uuid) |
|
||||||
if err != nil { |
|
||||||
if IsErrAttachmentNotExist(err) { |
|
||||||
continue |
|
||||||
} |
|
||||||
return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err) |
|
||||||
} |
|
||||||
attachments = append(attachments, attach) |
|
||||||
} |
|
||||||
|
|
||||||
for i := range attachments { |
|
||||||
attachments[i].IssueID = opts.Issue.ID |
|
||||||
attachments[i].CommentID = comment.ID |
|
||||||
// No assign value could be 0, so ignore AllCols().
|
|
||||||
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil { |
|
||||||
return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
case COMMENT_TYPE_REOPEN: |
|
||||||
act.OpType = ACTION_REOPEN_ISSUE |
|
||||||
if opts.Issue.IsPull { |
|
||||||
act.OpType = ACTION_REOPEN_PULL_REQUEST |
|
||||||
} |
|
||||||
|
|
||||||
if opts.Issue.IsPull { |
|
||||||
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) |
|
||||||
} else { |
|
||||||
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
case COMMENT_TYPE_CLOSE: |
|
||||||
act.OpType = ACTION_CLOSE_ISSUE |
|
||||||
if opts.Issue.IsPull { |
|
||||||
act.OpType = ACTION_CLOSE_PULL_REQUEST |
|
||||||
} |
|
||||||
|
|
||||||
if opts.Issue.IsPull { |
|
||||||
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) |
|
||||||
} else { |
|
||||||
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Notify watchers for whatever action comes in, ignore if no action type
|
|
||||||
if act.OpType > 0 { |
|
||||||
if err = notifyWatchers(e, act); err != nil { |
|
||||||
return nil, fmt.Errorf("notifyWatchers: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return comment, nil |
|
||||||
} |
|
||||||
|
|
||||||
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { |
|
||||||
cmtType := COMMENT_TYPE_CLOSE |
|
||||||
if !issue.IsClosed { |
|
||||||
cmtType = COMMENT_TYPE_REOPEN |
|
||||||
} |
|
||||||
return createComment(e, &CreateCommentOptions{ |
|
||||||
Type: cmtType, |
|
||||||
Doer: doer, |
|
||||||
Repo: repo, |
|
||||||
Issue: issue, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
type CreateCommentOptions struct { |
|
||||||
Type CommentType |
|
||||||
Doer *User |
|
||||||
Repo *Repository |
|
||||||
Issue *Issue |
|
||||||
|
|
||||||
CommitID int64 |
|
||||||
CommitSHA string |
|
||||||
LineNum int64 |
|
||||||
Content string |
|
||||||
Attachments []string // UUIDs of attachments
|
|
||||||
} |
|
||||||
|
|
||||||
// CreateComment creates comment of issue or commit.
|
|
||||||
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { |
|
||||||
sess := x.NewSession() |
|
||||||
defer sessionRelease(sess) |
|
||||||
if err = sess.Begin(); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
comment, err = createComment(sess, opts) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
return comment, sess.Commit() |
|
||||||
} |
|
||||||
|
|
||||||
// CreateIssueComment creates a plain issue comment.
|
|
||||||
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { |
|
||||||
return CreateComment(&CreateCommentOptions{ |
|
||||||
Type: COMMENT_TYPE_COMMENT, |
|
||||||
Doer: doer, |
|
||||||
Repo: repo, |
|
||||||
Issue: issue, |
|
||||||
Content: content, |
|
||||||
Attachments: attachments, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// CreateRefComment creates a commit reference comment to issue.
|
|
||||||
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { |
|
||||||
if len(commitSHA) == 0 { |
|
||||||
return fmt.Errorf("cannot create reference with empty commit SHA") |
|
||||||
} |
|
||||||
|
|
||||||
// Check if same reference from same commit has already existed.
|
|
||||||
has, err := x.Get(&Comment{ |
|
||||||
Type: COMMENT_TYPE_COMMIT_REF, |
|
||||||
IssueID: issue.ID, |
|
||||||
CommitSHA: commitSHA, |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return fmt.Errorf("check reference comment: %v", err) |
|
||||||
} else if has { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
_, err = CreateComment(&CreateCommentOptions{ |
|
||||||
Type: COMMENT_TYPE_COMMIT_REF, |
|
||||||
Doer: doer, |
|
||||||
Repo: repo, |
|
||||||
Issue: issue, |
|
||||||
CommitSHA: commitSHA, |
|
||||||
Content: content, |
|
||||||
}) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// GetCommentByID returns the comment by given ID.
|
|
||||||
func GetCommentByID(id int64) (*Comment, error) { |
|
||||||
c := new(Comment) |
|
||||||
has, err := x.Id(id).Get(c) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} else if !has { |
|
||||||
return nil, ErrCommentNotExist{id} |
|
||||||
} |
|
||||||
return c, nil |
|
||||||
} |
|
||||||
|
|
||||||
// GetCommentsByIssueID returns all comments of issue by given ID.
|
|
||||||
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) { |
|
||||||
comments := make([]*Comment, 0, 10) |
|
||||||
return comments, x.Where("issue_id=?", issueID).Asc("created_unix").Find(&comments) |
|
||||||
} |
|
||||||
|
|
||||||
// UpdateComment updates information of comment.
|
|
||||||
func UpdateComment(c *Comment) error { |
|
||||||
_, err := x.Id(c.ID).AllCols().Update(c) |
|
||||||
return err |
|
||||||
} |
|
@ -0,0 +1,174 @@ |
|||||||
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package models |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
log "gopkg.in/clog.v1" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/pkg/mailer" |
||||||
|
"github.com/gogits/gogs/pkg/markup" |
||||||
|
"github.com/gogits/gogs/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
func (issue *Issue) MailSubject() string { |
||||||
|
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index) |
||||||
|
} |
||||||
|
|
||||||
|
// mailerUser is a wrapper for satisfying mailer.User interface.
|
||||||
|
type mailerUser struct { |
||||||
|
user *User |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerUser) ID() int64 { |
||||||
|
return this.user.ID |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerUser) DisplayName() string { |
||||||
|
return this.user.DisplayName() |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerUser) Email() string { |
||||||
|
return this.user.Email |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerUser) GenerateActivateCode() string { |
||||||
|
return this.user.GenerateActivateCode() |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerUser) GenerateEmailActivateCode(email string) string { |
||||||
|
return this.user.GenerateEmailActivateCode(email) |
||||||
|
} |
||||||
|
|
||||||
|
func NewMailerUser(u *User) mailer.User { |
||||||
|
return mailerUser{u} |
||||||
|
} |
||||||
|
|
||||||
|
// mailerRepo is a wrapper for satisfying mailer.Repository interface.
|
||||||
|
type mailerRepo struct { |
||||||
|
repo *Repository |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerRepo) FullName() string { |
||||||
|
return this.repo.FullName() |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerRepo) HTMLURL() string { |
||||||
|
return this.repo.HTMLURL() |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerRepo) ComposeMetas() map[string]string { |
||||||
|
return this.repo.ComposeMetas() |
||||||
|
} |
||||||
|
|
||||||
|
func NewMailerRepo(repo *Repository) mailer.Repository { |
||||||
|
return mailerRepo{repo} |
||||||
|
} |
||||||
|
|
||||||
|
// mailerIssue is a wrapper for satisfying mailer.Issue interface.
|
||||||
|
type mailerIssue struct { |
||||||
|
issue *Issue |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerIssue) MailSubject() string { |
||||||
|
return this.issue.MailSubject() |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerIssue) Content() string { |
||||||
|
return this.issue.Content |
||||||
|
} |
||||||
|
|
||||||
|
func (this mailerIssue) HTMLURL() string { |
||||||
|
return this.issue.HTMLURL() |
||||||
|
} |
||||||
|
|
||||||
|
func NewMailerIssue(issue *Issue) mailer.Issue { |
||||||
|
return mailerIssue{issue} |
||||||
|
} |
||||||
|
|
||||||
|
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
|
||||||
|
// This functions sends two list of emails:
|
||||||
|
// 1. Repository watchers and users who are participated in comments.
|
||||||
|
// 2. Users who are not in 1. but get mentioned in current issue/comment.
|
||||||
|
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error { |
||||||
|
if !setting.Service.EnableNotifyMail { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
watchers, err := GetWatchers(issue.RepoID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err) |
||||||
|
} |
||||||
|
participants, err := GetParticipantsByIssueID(issue.ID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err) |
||||||
|
} |
||||||
|
|
||||||
|
// In case the issue poster is not watching the repository,
|
||||||
|
// even if we have duplicated in watchers, can be safely filtered out.
|
||||||
|
if issue.PosterID != doer.ID { |
||||||
|
participants = append(participants, issue.Poster) |
||||||
|
} |
||||||
|
|
||||||
|
tos := make([]string, 0, len(watchers)) // List of email addresses
|
||||||
|
names := make([]string, 0, len(watchers)) |
||||||
|
for i := range watchers { |
||||||
|
if watchers[i].UserID == doer.ID { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
to, err := GetUserByID(watchers[i].UserID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err) |
||||||
|
} |
||||||
|
if to.IsOrganization() { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
tos = append(tos, to.Email) |
||||||
|
names = append(names, to.Name) |
||||||
|
} |
||||||
|
for i := range participants { |
||||||
|
if participants[i].ID == doer.ID { |
||||||
|
continue |
||||||
|
} else if com.IsSliceContainsStr(names, participants[i].Name) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
tos = append(tos, participants[i].Email) |
||||||
|
names = append(names, participants[i].Name) |
||||||
|
} |
||||||
|
mailer.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos) |
||||||
|
|
||||||
|
// Mail mentioned people and exclude watchers.
|
||||||
|
names = append(names, doer.Name) |
||||||
|
tos = make([]string, 0, len(mentions)) // list of user names.
|
||||||
|
for i := range mentions { |
||||||
|
if com.IsSliceContainsStr(names, mentions[i]) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
tos = append(tos, mentions[i]) |
||||||
|
} |
||||||
|
mailer.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), GetUserEmailsByNames(tos)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// MailParticipants sends new issue thread created emails to repository watchers
|
||||||
|
// and mentioned people.
|
||||||
|
func (issue *Issue) MailParticipants() (err error) { |
||||||
|
mentions := markup.FindAllMentions(issue.Content) |
||||||
|
if err = updateIssueMentions(x, issue.ID, mentions); err != nil { |
||||||
|
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err) |
||||||
|
} |
||||||
|
|
||||||
|
if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil { |
||||||
|
log.Error(2, "mailIssueCommentToParticipants: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue