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] |
||||
init_cmds = [ |
||||
["make", "build-dev", "TAGS=sqlite"], |
||||
["make", "build-dev"], |
||||
["./gogs", "web"] |
||||
] |
||||
watch_all = true |
||||
watch_dirs = [ |
||||
"$WORKDIR/cmd", |
||||
"$WORKDIR/models", |
||||
"$WORKDIR/modules", |
||||
"$WORKDIR/routers" |
||||
"$WORKDIR/pkg", |
||||
"$WORKDIR/routes" |
||||
] |
||||
watch_exts = [".go"] |
||||
ignore_files = [".+_test.go"] |
||||
build_delay = 1500 |
||||
interrupt_timout = 1 |
||||
graceful_kill = true |
||||
cmds = [ |
||||
["make", "build-dev", "TAGS=sqlite"], # cert pam tidb |
||||
["make", "build-dev"], # TAGS=sqlite cert pam tidb |
||||
["./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 |
||||
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