mirror of https://github.com/gogits/gogs.git
Potato_lin
8 years ago
committed by
GitHub
696 changed files with 134842 additions and 6711 deletions
@ -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,11 @@
|
||||
public/conf/gitignore/* linguist-vendored |
||||
public/conf/license/* linguist-vendored |
||||
public/assets/* linguist-vendored |
||||
public/plugins/* linguist-vendored |
||||
public/plugins/* linguist-vendored |
||||
public/css/themes/* linguist-vendored |
||||
public/css/github.min.css linguist-vendored |
||||
public/css/semantic-2.2.1.min.css linguist-vendored |
||||
public/js/libs/* linguist-vendored |
||||
public/js/jquery-1.11.3.min.js linguist-vendored |
||||
public/js/semantic-2.2.1.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,70 @@
|
||||
// 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" |
||||
|
||||
"github.com/urfave/cli" |
||||
|
||||
"github.com/gogits/gogs/models" |
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
var ( |
||||
CmdAdmin = cli.Command{ |
||||
Name: "admin", |
||||
Usage: "Preform 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, |
||||
}, |
||||
} |
||||
|
||||
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"), |
||||
}, |
||||
} |
||||
) |
||||
|
||||
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 |
||||
} |
@ -0,0 +1,7 @@
|
||||
#ee0701 bug |
||||
#cccccc duplicate |
||||
#84b6eb enhancement |
||||
#128a0c help wanted |
||||
#e6e6e6 invalid |
||||
#cc317c question |
||||
#ffffff wontfix |
@ -0,0 +1,81 @@
|
||||
// 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" |
||||
|
||||
"github.com/gogits/gogs/modules/log" |
||||
"github.com/gogits/gogs/modules/markdown" |
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
func (issue *Issue) MailSubject() string { |
||||
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index) |
||||
} |
||||
|
||||
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
|
||||
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error { |
||||
if !setting.Service.EnableNotifyMail { |
||||
return nil |
||||
} |
||||
|
||||
// Mail wahtcers.
|
||||
watchers, err := GetWatchers(issue.RepoID) |
||||
if err != nil { |
||||
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err) |
||||
} |
||||
|
||||
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) |
||||
} |
||||
SendIssueCommentMail(issue, 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]) |
||||
} |
||||
SendIssueMentionMail(issue, 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 := markdown.FindAllMentions(issue.Content) |
||||
if err = UpdateIssueMentions(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(4, "mailIssueCommentToParticipants: %v", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -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 models |
||||
|
||||
import ( |
||||
"fmt" |
||||
"html/template" |
||||
"path" |
||||
|
||||
"gopkg.in/gomail.v2" |
||||
"gopkg.in/macaron.v1" |
||||
|
||||
"github.com/gogits/gogs/modules/base" |
||||
"github.com/gogits/gogs/modules/log" |
||||
"github.com/gogits/gogs/modules/mailer" |
||||
"github.com/gogits/gogs/modules/markdown" |
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
const ( |
||||
MAIL_AUTH_ACTIVATE base.TplName = "auth/activate" |
||||
MAIL_AUTH_ACTIVATE_EMAIL base.TplName = "auth/activate_email" |
||||
MAIL_AUTH_RESET_PASSWORD base.TplName = "auth/reset_passwd" |
||||
MAIL_AUTH_REGISTER_NOTIFY base.TplName = "auth/register_notify" |
||||
|
||||
MAIL_ISSUE_COMMENT base.TplName = "issue/comment" |
||||
MAIL_ISSUE_MENTION base.TplName = "issue/mention" |
||||
|
||||
MAIL_NOTIFY_COLLABORATOR base.TplName = "notify/collaborator" |
||||
) |
||||
|
||||
type MailRender interface { |
||||
HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) |
||||
} |
||||
|
||||
var mailRender MailRender |
||||
|
||||
func InitMailRender(dir, appendDir string, funcMap []template.FuncMap) { |
||||
opt := &macaron.RenderOptions{ |
||||
Directory: dir, |
||||
AppendDirectories: []string{appendDir}, |
||||
Funcs: funcMap, |
||||
Extensions: []string{".tmpl", ".html"}, |
||||
} |
||||
ts := macaron.NewTemplateSet() |
||||
ts.Set(macaron.DEFAULT_TPL_SET_NAME, opt) |
||||
|
||||
mailRender = &macaron.TplRender{ |
||||
TemplateSet: ts, |
||||
Opt: opt, |
||||
} |
||||
} |
||||
|
||||
func SendTestMail(email string) error { |
||||
return gomail.Send(&mailer.Sender{}, mailer.NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message) |
||||
} |
||||
|
||||
func SendUserMail(c *macaron.Context, u *User, tpl base.TplName, code, subject, info string) { |
||||
data := map[string]interface{}{ |
||||
"Username": u.DisplayName(), |
||||
"ActiveCodeLives": setting.Service.ActiveCodeLives / 60, |
||||
"ResetPwdCodeLives": setting.Service.ResetPwdCodeLives / 60, |
||||
"Code": code, |
||||
} |
||||
body, err := mailRender.HTMLString(string(tpl), data) |
||||
if err != nil { |
||||
log.Error(3, "HTMLString: %v", err) |
||||
return |
||||
} |
||||
|
||||
msg := mailer.NewMessage([]string{u.Email}, subject, body) |
||||
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info) |
||||
|
||||
mailer.SendAsync(msg) |
||||
} |
||||
|
||||
func SendActivateAccountMail(c *macaron.Context, u *User) { |
||||
SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account") |
||||
} |
||||
|
||||
func SendResetPasswordMail(c *macaron.Context, u *User) { |
||||
SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password") |
||||
} |
||||
|
||||
// SendActivateAccountMail sends confirmation email.
|
||||
func SendActivateEmailMail(c *macaron.Context, u *User, email *EmailAddress) { |
||||
data := map[string]interface{}{ |
||||
"Username": u.DisplayName(), |
||||
"ActiveCodeLives": setting.Service.ActiveCodeLives / 60, |
||||
"Code": u.GenerateEmailActivateCode(email.Email), |
||||
"Email": email.Email, |
||||
} |
||||
body, err := mailRender.HTMLString(string(MAIL_AUTH_ACTIVATE_EMAIL), data) |
||||
if err != nil { |
||||
log.Error(3, "HTMLString: %v", err) |
||||
return |
||||
} |
||||
|
||||
msg := mailer.NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), body) |
||||
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID) |
||||
|
||||
mailer.SendAsync(msg) |
||||
} |
||||
|
||||
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
|
||||
func SendRegisterNotifyMail(c *macaron.Context, u *User) { |
||||
data := map[string]interface{}{ |
||||
"Username": u.DisplayName(), |
||||
} |
||||
body, err := mailRender.HTMLString(string(MAIL_AUTH_REGISTER_NOTIFY), data) |
||||
if err != nil { |
||||
log.Error(3, "HTMLString: %v", err) |
||||
return |
||||
} |
||||
|
||||
msg := mailer.NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), body) |
||||
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID) |
||||
|
||||
mailer.SendAsync(msg) |
||||
} |
||||
|
||||
// SendCollaboratorMail sends mail notification to new collaborator.
|
||||
func SendCollaboratorMail(u, doer *User, repo *Repository) { |
||||
repoName := path.Join(repo.Owner.Name, repo.Name) |
||||
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) |
||||
|
||||
data := map[string]interface{}{ |
||||
"Subject": subject, |
||||
"RepoName": repoName, |
||||
"Link": repo.HTMLURL(), |
||||
} |
||||
body, err := mailRender.HTMLString(string(MAIL_NOTIFY_COLLABORATOR), data) |
||||
if err != nil { |
||||
log.Error(3, "HTMLString: %v", err) |
||||
return |
||||
} |
||||
|
||||
msg := mailer.NewMessage([]string{u.Email}, subject, body) |
||||
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID) |
||||
|
||||
mailer.SendAsync(msg) |
||||
} |
||||
|
||||
func composeTplData(subject, body, link string) map[string]interface{} { |
||||
data := make(map[string]interface{}, 10) |
||||
data["Subject"] = subject |
||||
data["Body"] = body |
||||
data["Link"] = link |
||||
return data |
||||
} |
||||
|
||||
func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message { |
||||
subject := issue.MailSubject() |
||||
body := string(markdown.RenderSpecialLink([]byte(issue.Content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) |
||||
data := composeTplData(subject, body, issue.HTMLURL()) |
||||
data["Doer"] = doer |
||||
content, err := mailRender.HTMLString(string(tplName), data) |
||||
if err != nil { |
||||
log.Error(3, "HTMLString (%s): %v", tplName, err) |
||||
} |
||||
msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.User), subject, content) |
||||
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) |
||||
return msg |
||||
} |
||||
|
||||
// SendIssueCommentMail composes and sends issue comment emails to target receivers.
|
||||
func SendIssueCommentMail(issue *Issue, doer *User, tos []string) { |
||||
if len(tos) == 0 { |
||||
return |
||||
} |
||||
|
||||
mailer.SendAsync(composeIssueMessage(issue, doer, MAIL_ISSUE_COMMENT, tos, "issue comment")) |
||||
} |
||||
|
||||
// SendIssueMentionMail composes and sends issue mention emails to target receivers.
|
||||
func SendIssueMentionMail(issue *Issue, doer *User, tos []string) { |
||||
if len(tos) == 0 { |
||||
return |
||||
} |
||||
mailer.SendAsync(composeIssueMessage(issue, doer, MAIL_ISSUE_MENTION, tos, "issue mention")) |
||||
} |
@ -0,0 +1,52 @@
|
||||
// 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 migrations |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/go-xorm/xorm" |
||||
) |
||||
|
||||
func ldapUseSSLToSecurityProtocol(x *xorm.Engine) error { |
||||
results, err := x.Query("SELECT `id`,`cfg` FROM `login_source` WHERE `type` = 2 OR `type` = 5") |
||||
if err != nil { |
||||
if strings.Contains(err.Error(), "no such column") { |
||||
return nil |
||||
} |
||||
return fmt.Errorf("select LDAP login sources: %v", err) |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, result := range results { |
||||
cfg := map[string]interface{}{} |
||||
if err = json.Unmarshal(result["cfg"], &cfg); err != nil { |
||||
return fmt.Errorf("decode JSON config: %v", err) |
||||
} |
||||
if com.ToStr(cfg["UseSSL"]) == "true" { |
||||
cfg["SecurityProtocol"] = 1 // LDAPS
|
||||
} |
||||
delete(cfg, "UseSSL") |
||||
|
||||
data, err := json.Marshal(&cfg) |
||||
if err != nil { |
||||
return fmt.Errorf("encode JSON config: %v", err) |
||||
} |
||||
|
||||
if _, err = sess.Exec("UPDATE `login_source` SET `cfg`=? WHERE `id`=?", |
||||
string(data), com.StrTo(result["id"]).MustInt64()); err != nil { |
||||
return fmt.Errorf("update config column: %v", err) |
||||
} |
||||
} |
||||
return sess.Commit() |
||||
} |
@ -0,0 +1,24 @@
|
||||
// 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 migrations |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/go-xorm/xorm" |
||||
) |
||||
|
||||
func setCommentUpdatedWithCreated(x *xorm.Engine) (err error) { |
||||
type Comment struct { |
||||
UpdatedUnix int64 |
||||
} |
||||
|
||||
if err = x.Sync2(new(Comment)); err != nil { |
||||
return fmt.Errorf("Sync2: %v", err) |
||||
} else if _, err = x.Exec("UPDATE comment SET updated_unix = created_unix"); err != nil { |
||||
return fmt.Errorf("set update_unix: %v", err) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,33 @@
|
||||
// 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 ( |
||||
"testing" |
||||
|
||||
. "github.com/smartystreets/goconvey/convey" |
||||
) |
||||
|
||||
func Test_parsePostgreSQLHostPort(t *testing.T) { |
||||
testSuites := []struct { |
||||
input string |
||||
host, port string |
||||
}{ |
||||
{"127.0.0.1:1234", "127.0.0.1", "1234"}, |
||||
{"127.0.0.1", "127.0.0.1", "5432"}, |
||||
{"[::1]:1234", "[::1]", "1234"}, |
||||
{"[::1]", "[::1]", "5432"}, |
||||
{"/tmp/pg.sock:1234", "/tmp/pg.sock", "1234"}, |
||||
{"/tmp/pg.sock", "/tmp/pg.sock", "5432"}, |
||||
} |
||||
|
||||
Convey("Parse PostgreSQL host and port", t, func() { |
||||
for _, suite := range testSuites { |
||||
host, port := parsePostgreSQLHostPort(suite.input) |
||||
So(host, ShouldEqual, suite.host) |
||||
So(port, ShouldEqual, suite.port) |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,618 @@
|
||||
// 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 ( |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
) |
||||
|
||||
const OWNER_TEAM = "Owners" |
||||
|
||||
// Team represents a organization team.
|
||||
type Team struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
OrgID int64 `xorm:"INDEX"` |
||||
LowerName string |
||||
Name string |
||||
Description string |
||||
Authorize AccessMode |
||||
Repos []*Repository `xorm:"-"` |
||||
Members []*User `xorm:"-"` |
||||
NumRepos int |
||||
NumMembers int |
||||
} |
||||
|
||||
// IsOwnerTeam returns true if team is owner team.
|
||||
func (t *Team) IsOwnerTeam() bool { |
||||
return t.Name == OWNER_TEAM |
||||
} |
||||
|
||||
// IsTeamMember returns true if given user is a member of team.
|
||||
func (t *Team) IsMember(uid int64) bool { |
||||
return IsTeamMember(t.OrgID, t.ID, uid) |
||||
} |
||||
|
||||
func (t *Team) getRepositories(e Engine) (err error) { |
||||
teamRepos := make([]*TeamRepo, 0, t.NumRepos) |
||||
if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil { |
||||
return fmt.Errorf("get team-repos: %v", err) |
||||
} |
||||
|
||||
t.Repos = make([]*Repository, 0, len(teamRepos)) |
||||
for i := range teamRepos { |
||||
repo, err := getRepositoryByID(e, teamRepos[i].RepoID) |
||||
if err != nil { |
||||
return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err) |
||||
} |
||||
t.Repos = append(t.Repos, repo) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// GetRepositories returns all repositories in team of organization.
|
||||
func (t *Team) GetRepositories() error { |
||||
return t.getRepositories(x) |
||||
} |
||||
|
||||
func (t *Team) getMembers(e Engine) (err error) { |
||||
t.Members, err = getTeamMembers(e, t.ID) |
||||
return err |
||||
} |
||||
|
||||
// GetMembers returns all members in team of organization.
|
||||
func (t *Team) GetMembers() (err error) { |
||||
return t.getMembers(x) |
||||
} |
||||
|
||||
// AddMember adds new membership of the team to the organization,
|
||||
// the user will have membership to the organization automatically when needed.
|
||||
func (t *Team) AddMember(uid int64) error { |
||||
return AddTeamMember(t.OrgID, t.ID, uid) |
||||
} |
||||
|
||||
// RemoveMember removes member from team of organization.
|
||||
func (t *Team) RemoveMember(uid int64) error { |
||||
return RemoveTeamMember(t.OrgID, t.ID, uid) |
||||
} |
||||
|
||||
func (t *Team) hasRepository(e Engine, repoID int64) bool { |
||||
return hasTeamRepo(e, t.OrgID, t.ID, repoID) |
||||
} |
||||
|
||||
// HasRepository returns true if given repository belong to team.
|
||||
func (t *Team) HasRepository(repoID int64) bool { |
||||
return t.hasRepository(x, repoID) |
||||
} |
||||
|
||||
func (t *Team) addRepository(e Engine, repo *Repository) (err error) { |
||||
if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
t.NumRepos++ |
||||
if _, err = e.Id(t.ID).AllCols().Update(t); err != nil { |
||||
return fmt.Errorf("update team: %v", err) |
||||
} |
||||
|
||||
if err = repo.recalculateTeamAccesses(e, 0); err != nil { |
||||
return fmt.Errorf("recalculateAccesses: %v", err) |
||||
} |
||||
|
||||
if err = t.getMembers(e); err != nil { |
||||
return fmt.Errorf("getMembers: %v", err) |
||||
} |
||||
for _, u := range t.Members { |
||||
if err = watchRepo(e, u.ID, repo.ID, true); err != nil { |
||||
return fmt.Errorf("watchRepo: %v", err) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// AddRepository adds new repository to team of organization.
|
||||
func (t *Team) AddRepository(repo *Repository) (err error) { |
||||
if repo.OwnerID != t.OrgID { |
||||
return errors.New("Repository does not belong to organization") |
||||
} else if t.HasRepository(repo.ID) { |
||||
return nil |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = t.addRepository(sess, repo); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) { |
||||
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
t.NumRepos-- |
||||
if _, err = e.Id(t.ID).AllCols().Update(t); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Don't need to recalculate when delete a repository from organization.
|
||||
if recalculate { |
||||
if err = repo.recalculateTeamAccesses(e, t.ID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if err = t.getMembers(e); err != nil { |
||||
return fmt.Errorf("get team members: %v", err) |
||||
} |
||||
for _, u := range t.Members { |
||||
has, err := hasAccess(e, u, repo, ACCESS_MODE_READ) |
||||
if err != nil { |
||||
return err |
||||
} else if has { |
||||
continue |
||||
} |
||||
|
||||
if err = watchRepo(e, u.ID, repo.ID, false); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// RemoveRepository removes repository from team of organization.
|
||||
func (t *Team) RemoveRepository(repoID int64) error { |
||||
if !t.HasRepository(repoID) { |
||||
return nil |
||||
} |
||||
|
||||
repo, err := GetRepositoryByID(repoID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = t.removeRepository(sess, repo, true); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
// NewTeam creates a record of new team.
|
||||
// It's caller's responsibility to assign organization ID.
|
||||
func NewTeam(t *Team) error { |
||||
if len(t.Name) == 0 { |
||||
return errors.New("empty team name") |
||||
} |
||||
|
||||
has, err := x.Id(t.OrgID).Get(new(User)) |
||||
if err != nil { |
||||
return err |
||||
} else if !has { |
||||
return ErrOrgNotExist |
||||
} |
||||
|
||||
t.LowerName = strings.ToLower(t.Name) |
||||
has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(new(Team)) |
||||
if err != nil { |
||||
return err |
||||
} else if has { |
||||
return ErrTeamAlreadyExist{t.OrgID, t.LowerName} |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sess.Close() |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err = sess.Insert(t); err != nil { |
||||
sess.Rollback() |
||||
return err |
||||
} |
||||
|
||||
// Update organization number of teams.
|
||||
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { |
||||
sess.Rollback() |
||||
return err |
||||
} |
||||
return sess.Commit() |
||||
} |
||||
|
||||
func getTeam(e Engine, orgId int64, name string) (*Team, error) { |
||||
t := &Team{ |
||||
OrgID: orgId, |
||||
LowerName: strings.ToLower(name), |
||||
} |
||||
has, err := e.Get(t) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrTeamNotExist |
||||
} |
||||
return t, nil |
||||
} |
||||
|
||||
// GetTeam returns team by given team name and organization.
|
||||
func GetTeam(orgId int64, name string) (*Team, error) { |
||||
return getTeam(x, orgId, name) |
||||
} |
||||
|
||||
func getTeamByID(e Engine, teamId int64) (*Team, error) { |
||||
t := new(Team) |
||||
has, err := e.Id(teamId).Get(t) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrTeamNotExist |
||||
} |
||||
return t, nil |
||||
} |
||||
|
||||
// GetTeamByID returns team by given ID.
|
||||
func GetTeamByID(teamId int64) (*Team, error) { |
||||
return getTeamByID(x, teamId) |
||||
} |
||||
|
||||
// UpdateTeam updates information of team.
|
||||
func UpdateTeam(t *Team, authChanged bool) (err error) { |
||||
if len(t.Name) == 0 { |
||||
return errors.New("empty team name") |
||||
} |
||||
|
||||
if len(t.Description) > 255 { |
||||
t.Description = t.Description[:255] |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
t.LowerName = strings.ToLower(t.Name) |
||||
has, err := x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).And("id!=?", t.ID).Get(new(Team)) |
||||
if err != nil { |
||||
return err |
||||
} else if has { |
||||
return ErrTeamAlreadyExist{t.OrgID, t.LowerName} |
||||
} |
||||
|
||||
if _, err = sess.Id(t.ID).AllCols().Update(t); err != nil { |
||||
return fmt.Errorf("update: %v", err) |
||||
} |
||||
|
||||
// Update access for team members if needed.
|
||||
if authChanged { |
||||
if err = t.getRepositories(sess); err != nil { |
||||
return fmt.Errorf("getRepositories:%v", err) |
||||
} |
||||
|
||||
for _, repo := range t.Repos { |
||||
if err = repo.recalculateTeamAccesses(sess, 0); err != nil { |
||||
return fmt.Errorf("recalculateTeamAccesses: %v", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
// DeleteTeam deletes given team.
|
||||
// It's caller's responsibility to assign organization ID.
|
||||
func DeleteTeam(t *Team) error { |
||||
if err := t.GetRepositories(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Get organization.
|
||||
org, err := GetUserByID(t.OrgID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Delete all accesses.
|
||||
for _, repo := range t.Repos { |
||||
if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// Delete team-user.
|
||||
if _, err = sess.Where("org_id=?", org.ID).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Delete team.
|
||||
if _, err = sess.Id(t.ID).Delete(new(Team)); err != nil { |
||||
return err |
||||
} |
||||
// Update organization number of teams.
|
||||
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
// ___________ ____ ___
|
||||
// \__ ___/___ _____ _____ | | \______ ___________
|
||||
// | |_/ __ \\__ \ / \| | / ___// __ \_ __ \
|
||||
// | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/
|
||||
// |____| \___ >____ /__|_| /______//____ >\___ >__|
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// TeamUser represents an team-user relation.
|
||||
type TeamUser struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
OrgID int64 `xorm:"INDEX"` |
||||
TeamID int64 `xorm:"UNIQUE(s)"` |
||||
Uid int64 `xorm:"UNIQUE(s)"` |
||||
} |
||||
|
||||
func isTeamMember(e Engine, orgID, teamID, uid int64) bool { |
||||
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser)) |
||||
return has |
||||
} |
||||
|
||||
// IsTeamMember returns true if given user is a member of team.
|
||||
func IsTeamMember(orgID, teamID, uid int64) bool { |
||||
return isTeamMember(x, orgID, teamID, uid) |
||||
} |
||||
|
||||
func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) { |
||||
teamUsers := make([]*TeamUser, 0, 10) |
||||
if err = e.Where("team_id=?", teamID).Find(&teamUsers); err != nil { |
||||
return nil, fmt.Errorf("get team-users: %v", err) |
||||
} |
||||
members := make([]*User, 0, len(teamUsers)) |
||||
for i := range teamUsers { |
||||
member := new(User) |
||||
if _, err = e.Id(teamUsers[i].Uid).Get(member); err != nil { |
||||
return nil, fmt.Errorf("get user '%d': %v", teamUsers[i].Uid, err) |
||||
} |
||||
members = append(members, member) |
||||
} |
||||
return members, nil |
||||
} |
||||
|
||||
// GetTeamMembers returns all members in given team of organization.
|
||||
func GetTeamMembers(teamID int64) ([]*User, error) { |
||||
return getTeamMembers(x, teamID) |
||||
} |
||||
|
||||
func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) { |
||||
tus := make([]*TeamUser, 0, 5) |
||||
if err := e.Where("uid=?", uid).And("org_id=?", orgId).Find(&tus); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
ts := make([]*Team, len(tus)) |
||||
for i, tu := range tus { |
||||
t := new(Team) |
||||
has, err := e.Id(tu.TeamID).Get(t) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrTeamNotExist |
||||
} |
||||
ts[i] = t |
||||
} |
||||
return ts, nil |
||||
} |
||||
|
||||
// GetUserTeams returns all teams that user belongs to in given organization.
|
||||
func GetUserTeams(orgId, uid int64) ([]*Team, error) { |
||||
return getUserTeams(x, orgId, uid) |
||||
} |
||||
|
||||
// AddTeamMember adds new membership of given team to given organization,
|
||||
// the user will have membership to given organization automatically when needed.
|
||||
func AddTeamMember(orgID, teamID, uid int64) error { |
||||
if IsTeamMember(orgID, teamID, uid) { |
||||
return nil |
||||
} |
||||
|
||||
if err := AddOrgUser(orgID, uid); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Get team and its repositories.
|
||||
t, err := GetTeamByID(teamID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
t.NumMembers++ |
||||
|
||||
if err = t.GetRepositories(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
tu := &TeamUser{ |
||||
Uid: uid, |
||||
OrgID: orgID, |
||||
TeamID: teamID, |
||||
} |
||||
if _, err = sess.Insert(tu); err != nil { |
||||
return err |
||||
} else if _, err = sess.Id(t.ID).Update(t); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Give access to team repositories.
|
||||
for _, repo := range t.Repos { |
||||
if err = repo.recalculateTeamAccesses(sess, 0); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// We make sure it exists before.
|
||||
ou := new(OrgUser) |
||||
if _, err = sess.Where("uid = ?", uid).And("org_id = ?", orgID).Get(ou); err != nil { |
||||
return err |
||||
} |
||||
ou.NumTeams++ |
||||
if t.IsOwnerTeam() { |
||||
ou.IsOwner = true |
||||
} |
||||
if _, err = sess.Id(ou.ID).AllCols().Update(ou); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
func removeTeamMember(e Engine, orgID, teamID, uid int64) error { |
||||
if !isTeamMember(e, orgID, teamID, uid) { |
||||
return nil |
||||
} |
||||
|
||||
// Get team and its repositories.
|
||||
t, err := getTeamByID(e, teamID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Check if the user to delete is the last member in owner team.
|
||||
if t.IsOwnerTeam() && t.NumMembers == 1 { |
||||
return ErrLastOrgOwner{UID: uid} |
||||
} |
||||
|
||||
t.NumMembers-- |
||||
|
||||
if err = t.getRepositories(e); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Get organization.
|
||||
org, err := getUserByID(e, orgID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
tu := &TeamUser{ |
||||
Uid: uid, |
||||
OrgID: orgID, |
||||
TeamID: teamID, |
||||
} |
||||
if _, err := e.Delete(tu); err != nil { |
||||
return err |
||||
} else if _, err = e.Id(t.ID).AllCols().Update(t); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Delete access to team repositories.
|
||||
for _, repo := range t.Repos { |
||||
if err = repo.recalculateTeamAccesses(e, 0); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// This must exist.
|
||||
ou := new(OrgUser) |
||||
_, err = e.Where("uid = ?", uid).And("org_id = ?", org.ID).Get(ou) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
ou.NumTeams-- |
||||
if t.IsOwnerTeam() { |
||||
ou.IsOwner = false |
||||
} |
||||
if _, err = e.Id(ou.ID).AllCols().Update(ou); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// RemoveTeamMember removes member from given team of given organization.
|
||||
func RemoveTeamMember(orgID, teamID, uid int64) error { |
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err := sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
if err := removeTeamMember(sess, orgID, teamID, uid); err != nil { |
||||
return err |
||||
} |
||||
return sess.Commit() |
||||
} |
||||
|
||||
// ___________ __________
|
||||
// \__ ___/___ _____ _____\______ \ ____ ______ ____
|
||||
// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \
|
||||
// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> )
|
||||
// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/
|
||||
// \/ \/ \/ \/ \/|__|
|
||||
|
||||
// TeamRepo represents an team-repository relation.
|
||||
type TeamRepo struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
OrgID int64 `xorm:"INDEX"` |
||||
TeamID int64 `xorm:"UNIQUE(s)"` |
||||
RepoID int64 `xorm:"UNIQUE(s)"` |
||||
} |
||||
|
||||
func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool { |
||||
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("repo_id=?", repoID).Get(new(TeamRepo)) |
||||
return has |
||||
} |
||||
|
||||
// HasTeamRepo returns true if given repository belongs to team.
|
||||
func HasTeamRepo(orgID, teamID, repoID int64) bool { |
||||
return hasTeamRepo(x, orgID, teamID, repoID) |
||||
} |
||||
|
||||
func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { |
||||
_, err := e.InsertOne(&TeamRepo{ |
||||
OrgID: orgID, |
||||
TeamID: teamID, |
||||
RepoID: repoID, |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
// AddTeamRepo adds new repository relation to team.
|
||||
func AddTeamRepo(orgID, teamID, repoID int64) error { |
||||
return addTeamRepo(x, orgID, teamID, repoID) |
||||
} |
||||
|
||||
func removeTeamRepo(e Engine, teamID, repoID int64) error { |
||||
_, err := e.Delete(&TeamRepo{ |
||||
TeamID: teamID, |
||||
RepoID: repoID, |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
// RemoveTeamRepo deletes repository relation to team.
|
||||
func RemoveTeamRepo(teamID, repoID int64) error { |
||||
return removeTeamRepo(x, teamID, repoID) |
||||
} |
@ -0,0 +1,520 @@
|
||||
// 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" |
||||
"io" |
||||
"io/ioutil" |
||||
"mime/multipart" |
||||
"os" |
||||
"os/exec" |
||||
"path" |
||||
"path/filepath" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
gouuid "github.com/satori/go.uuid" |
||||
|
||||
git "github.com/gogits/git-module" |
||||
|
||||
"github.com/gogits/gogs/modules/log" |
||||
"github.com/gogits/gogs/modules/process" |
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
// ___________ .___.__ __ ___________.__.__
|
||||
// \_ _____/ __| _/|__|/ |_ \_ _____/|__| | ____
|
||||
// | __)_ / __ | | \ __\ | __) | | | _/ __ \
|
||||
// | \/ /_/ | | || | | \ | | |_\ ___/
|
||||
// /_______ /\____ | |__||__| \___ / |__|____/\___ >
|
||||
// \/ \/ \/ \/
|
||||
|
||||
// discardLocalRepoBranchChanges discards local commits/changes of
|
||||
// given branch to make sure it is even to remote branch.
|
||||
func discardLocalRepoBranchChanges(localPath, branch string) error { |
||||
if !com.IsExist(localPath) { |
||||
return nil |
||||
} |
||||
// No need to check if nothing in the repository.
|
||||
if !git.IsBranchExist(localPath, branch) { |
||||
return nil |
||||
} |
||||
|
||||
refName := "origin/" + branch |
||||
if err := git.ResetHEAD(localPath, true, refName); err != nil { |
||||
return fmt.Errorf("git reset --hard %s: %v", refName, err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error { |
||||
return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch) |
||||
} |
||||
|
||||
// checkoutNewBranch checks out to a new branch from the a branch name.
|
||||
func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error { |
||||
if err := git.Checkout(localPath, git.CheckoutOptions{ |
||||
Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second, |
||||
Branch: newBranch, |
||||
OldBranch: oldBranch, |
||||
}); err != nil { |
||||
return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error { |
||||
return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch) |
||||
} |
||||
|
||||
type UpdateRepoFileOptions struct { |
||||
LastCommitID string |
||||
OldBranch string |
||||
NewBranch string |
||||
OldTreeName string |
||||
NewTreeName string |
||||
Message string |
||||
Content string |
||||
IsNewFile bool |
||||
} |
||||
|
||||
// UpdateRepoFile adds or updates a file in repository.
|
||||
func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (err error) { |
||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID)) |
||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) |
||||
|
||||
if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil { |
||||
return fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", opts.OldBranch, err) |
||||
} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil { |
||||
return fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", opts.OldBranch, err) |
||||
} |
||||
|
||||
if opts.OldBranch != opts.NewBranch { |
||||
if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil { |
||||
return fmt.Errorf("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v", opts.OldBranch, opts.NewBranch, err) |
||||
} |
||||
} |
||||
|
||||
localPath := repo.LocalCopyPath() |
||||
oldFilePath := path.Join(localPath, opts.OldTreeName) |
||||
filePath := path.Join(localPath, opts.NewTreeName) |
||||
os.MkdirAll(path.Dir(filePath), os.ModePerm) |
||||
|
||||
// If it's meant to be a new file, make sure it doesn't exist.
|
||||
if opts.IsNewFile { |
||||
if com.IsExist(filePath) { |
||||
return ErrRepoFileAlreadyExist{filePath} |
||||
} |
||||
} |
||||
|
||||
// Ignore move step if it's a new file under a directory.
|
||||
// Otherwise, move the file when name changed.
|
||||
if com.IsFile(oldFilePath) && opts.OldTreeName != opts.NewTreeName { |
||||
if err = git.MoveFile(localPath, opts.OldTreeName, opts.NewTreeName); err != nil { |
||||
return fmt.Errorf("git mv %s %s: %v", opts.OldTreeName, opts.NewTreeName, err) |
||||
} |
||||
} |
||||
|
||||
if err = ioutil.WriteFile(filePath, []byte(opts.Content), 0666); err != nil { |
||||
return fmt.Errorf("WriteFile: %v", err) |
||||
} |
||||
|
||||
if err = git.AddChanges(localPath, true); err != nil { |
||||
return fmt.Errorf("git add --all: %v", err) |
||||
} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ |
||||
Committer: doer.NewGitSig(), |
||||
Message: opts.Message, |
||||
}); err != nil { |
||||
return fmt.Errorf("CommitChanges: %v", err) |
||||
} else if err = git.Push(localPath, "origin", opts.NewBranch); err != nil { |
||||
return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) |
||||
} |
||||
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath()) |
||||
if err != nil { |
||||
log.Error(4, "OpenRepository: %v", err) |
||||
return nil |
||||
} |
||||
commit, err := gitRepo.GetBranchCommit(opts.NewBranch) |
||||
if err != nil { |
||||
log.Error(4, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err) |
||||
return nil |
||||
} |
||||
|
||||
// Simulate push event.
|
||||
pushCommits := &PushCommits{ |
||||
Len: 1, |
||||
Commits: []*PushCommit{CommitToPushCommit(commit)}, |
||||
} |
||||
oldCommitID := opts.LastCommitID |
||||
if opts.NewBranch != opts.OldBranch { |
||||
oldCommitID = git.EMPTY_SHA |
||||
} |
||||
if err := CommitRepoAction(CommitRepoActionOptions{ |
||||
PusherName: doer.Name, |
||||
RepoOwnerID: repo.MustOwner().ID, |
||||
RepoName: repo.Name, |
||||
RefFullName: git.BRANCH_PREFIX + opts.NewBranch, |
||||
OldCommitID: oldCommitID, |
||||
NewCommitID: commit.ID.String(), |
||||
Commits: pushCommits, |
||||
}); err != nil { |
||||
log.Error(4, "CommitRepoAction: %v", err) |
||||
return nil |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// GetDiffPreview produces and returns diff result of a file which is not yet committed.
|
||||
func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *Diff, err error) { |
||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID)) |
||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) |
||||
|
||||
if err = repo.DiscardLocalRepoBranchChanges(branch); err != nil { |
||||
return nil, fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", branch, err) |
||||
} else if err = repo.UpdateLocalCopyBranch(branch); err != nil { |
||||
return nil, fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", branch, err) |
||||
} |
||||
|
||||
localPath := repo.LocalCopyPath() |
||||
filePath := path.Join(localPath, treePath) |
||||
os.MkdirAll(filepath.Dir(filePath), os.ModePerm) |
||||
if err = ioutil.WriteFile(filePath, []byte(content), 0666); err != nil { |
||||
return nil, fmt.Errorf("WriteFile: %v", err) |
||||
} |
||||
|
||||
cmd := exec.Command("git", "diff", treePath) |
||||
cmd.Dir = localPath |
||||
cmd.Stderr = os.Stderr |
||||
|
||||
stdout, err := cmd.StdoutPipe() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("StdoutPipe: %v", err) |
||||
} |
||||
|
||||
if err = cmd.Start(); err != nil { |
||||
return nil, fmt.Errorf("Start: %v", err) |
||||
} |
||||
|
||||
pid := process.Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", repo.RepoPath()), cmd) |
||||
defer process.Remove(pid) |
||||
|
||||
diff, err = ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("ParsePatch: %v", err) |
||||
} |
||||
|
||||
if err = cmd.Wait(); err != nil { |
||||
return nil, fmt.Errorf("Wait: %v", err) |
||||
} |
||||
|
||||
return diff, nil |
||||
} |
||||
|
||||
// ________ .__ __ ___________.__.__
|
||||
// \______ \ ____ | | _____/ |_ ____ \_ _____/|__| | ____
|
||||
// | | \_/ __ \| | _/ __ \ __\/ __ \ | __) | | | _/ __ \
|
||||
// | ` \ ___/| |_\ ___/| | \ ___/ | \ | | |_\ ___/
|
||||
// /_______ /\___ >____/\___ >__| \___ > \___ / |__|____/\___ >
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
//
|
||||
|
||||
type DeleteRepoFileOptions struct { |
||||
LastCommitID string |
||||
OldBranch string |
||||
NewBranch string |
||||
TreePath string |
||||
Message string |
||||
} |
||||
|
||||
func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (err error) { |
||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID)) |
||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) |
||||
|
||||
if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil { |
||||
return fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", opts.OldBranch, err) |
||||
} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil { |
||||
return fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", opts.OldBranch, err) |
||||
} |
||||
|
||||
if opts.OldBranch != opts.NewBranch { |
||||
if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil { |
||||
return fmt.Errorf("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v", opts.OldBranch, opts.NewBranch, err) |
||||
} |
||||
} |
||||
|
||||
localPath := repo.LocalCopyPath() |
||||
if err = os.Remove(path.Join(localPath, opts.TreePath)); err != nil { |
||||
return fmt.Errorf("Remove: %v", err) |
||||
} |
||||
|
||||
if err = git.AddChanges(localPath, true); err != nil { |
||||
return fmt.Errorf("git add --all: %v", err) |
||||
} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ |
||||
Committer: doer.NewGitSig(), |
||||
Message: opts.Message, |
||||
}); err != nil { |
||||
return fmt.Errorf("CommitChanges: %v", err) |
||||
} else if err = git.Push(localPath, "origin", opts.NewBranch); err != nil { |
||||
return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) |
||||
} |
||||
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath()) |
||||
if err != nil { |
||||
log.Error(4, "OpenRepository: %v", err) |
||||
return nil |
||||
} |
||||
commit, err := gitRepo.GetBranchCommit(opts.NewBranch) |
||||
if err != nil { |
||||
log.Error(4, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err) |
||||
return nil |
||||
} |
||||
|
||||
// Simulate push event.
|
||||
pushCommits := &PushCommits{ |
||||
Len: 1, |
||||
Commits: []*PushCommit{CommitToPushCommit(commit)}, |
||||
} |
||||
if err := CommitRepoAction(CommitRepoActionOptions{ |
||||
PusherName: doer.Name, |
||||
RepoOwnerID: repo.MustOwner().ID, |
||||
RepoName: repo.Name, |
||||
RefFullName: git.BRANCH_PREFIX + opts.NewBranch, |
||||
OldCommitID: opts.LastCommitID, |
||||
NewCommitID: commit.ID.String(), |
||||
Commits: pushCommits, |
||||
}); err != nil { |
||||
log.Error(4, "CommitRepoAction: %v", err) |
||||
return nil |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// ____ ___ .__ .___ ___________.___.__
|
||||
// | | \______ | | _________ __| _/ \_ _____/| | | ____ ______
|
||||
// | | /\____ \| | / _ \__ \ / __ | | __) | | | _/ __ \ / ___/
|
||||
// | | / | |_> > |_( <_> ) __ \_/ /_/ | | \ | | |_\ ___/ \___ \
|
||||
// |______/ | __/|____/\____(____ /\____ | \___ / |___|____/\___ >____ >
|
||||
// |__| \/ \/ \/ \/ \/
|
||||
//
|
||||
|
||||
// Upload represent a uploaded file to a repo to be deleted when moved
|
||||
type Upload struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
UUID string `xorm:"uuid UNIQUE"` |
||||
Name string |
||||
} |
||||
|
||||
// UploadLocalPath returns where uploads is stored in local file system based on given UUID.
|
||||
func UploadLocalPath(uuid string) string { |
||||
return path.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid) |
||||
} |
||||
|
||||
// LocalPath returns where uploads are temporarily stored in local file system.
|
||||
func (upload *Upload) LocalPath() string { |
||||
return UploadLocalPath(upload.UUID) |
||||
} |
||||
|
||||
// NewUpload creates a new upload object.
|
||||
func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) { |
||||
upload := &Upload{ |
||||
UUID: gouuid.NewV4().String(), |
||||
Name: name, |
||||
} |
||||
|
||||
localPath := upload.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(upload); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return upload, nil |
||||
} |
||||
|
||||
func GetUploadByUUID(uuid string) (*Upload, error) { |
||||
upload := &Upload{UUID: uuid} |
||||
has, err := x.Get(upload) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrUploadNotExist{0, uuid} |
||||
} |
||||
return upload, nil |
||||
} |
||||
|
||||
func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) { |
||||
if len(uuids) == 0 { |
||||
return []*Upload{}, nil |
||||
} |
||||
|
||||
// Silently drop invalid uuids.
|
||||
uploads := make([]*Upload, 0, len(uuids)) |
||||
return uploads, x.In("uuid", uuids).Find(&uploads) |
||||
} |
||||
|
||||
func DeleteUploads(uploads ...*Upload) (err error) { |
||||
if len(uploads) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
ids := make([]int64, len(uploads)) |
||||
for i := 0; i < len(uploads); i++ { |
||||
ids[i] = uploads[i].ID |
||||
} |
||||
if _, err = sess.In("id", ids).Delete(new(Upload)); err != nil { |
||||
return fmt.Errorf("delete uploads: %v", err) |
||||
} |
||||
|
||||
for _, upload := range uploads { |
||||
localPath := upload.LocalPath() |
||||
if !com.IsFile(localPath) { |
||||
continue |
||||
} |
||||
|
||||
if err := os.Remove(localPath); err != nil { |
||||
return fmt.Errorf("remove upload: %v", err) |
||||
} |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
func DeleteUpload(u *Upload) error { |
||||
return DeleteUploads(u) |
||||
} |
||||
|
||||
func DeleteUploadByUUID(uuid string) error { |
||||
upload, err := GetUploadByUUID(uuid) |
||||
if err != nil { |
||||
if IsErrUploadNotExist(err) { |
||||
return nil |
||||
} |
||||
return fmt.Errorf("GetUploadByUUID: %v", err) |
||||
} |
||||
|
||||
if err := DeleteUpload(upload); err != nil { |
||||
return fmt.Errorf("DeleteUpload: %v", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
type UploadRepoFileOptions struct { |
||||
LastCommitID string |
||||
OldBranch string |
||||
NewBranch string |
||||
TreePath string |
||||
Message string |
||||
Files []string // In UUID format.
|
||||
} |
||||
|
||||
func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) (err error) { |
||||
if len(opts.Files) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
uploads, err := GetUploadsByUUIDs(opts.Files) |
||||
if err != nil { |
||||
return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err) |
||||
} |
||||
|
||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID)) |
||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) |
||||
|
||||
if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil { |
||||
return fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", opts.OldBranch, err) |
||||
} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil { |
||||
return fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", opts.OldBranch, err) |
||||
} |
||||
|
||||
if opts.OldBranch != opts.NewBranch { |
||||
if err = repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil { |
||||
return fmt.Errorf("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v", opts.OldBranch, opts.NewBranch, err) |
||||
} |
||||
} |
||||
|
||||
localPath := repo.LocalCopyPath() |
||||
dirPath := path.Join(localPath, opts.TreePath) |
||||
os.MkdirAll(dirPath, os.ModePerm) |
||||
|
||||
// Copy uploaded files into repository.
|
||||
for _, upload := range uploads { |
||||
tmpPath := upload.LocalPath() |
||||
targetPath := path.Join(dirPath, upload.Name) |
||||
if !com.IsFile(tmpPath) { |
||||
continue |
||||
} |
||||
|
||||
if err = com.Copy(tmpPath, targetPath); err != nil { |
||||
return fmt.Errorf("Copy: %v", err) |
||||
} |
||||
} |
||||
|
||||
if err = git.AddChanges(localPath, true); err != nil { |
||||
return fmt.Errorf("git add --all: %v", err) |
||||
} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ |
||||
Committer: doer.NewGitSig(), |
||||
Message: opts.Message, |
||||
}); err != nil { |
||||
return fmt.Errorf("CommitChanges: %v", err) |
||||
} else if err = git.Push(localPath, "origin", opts.NewBranch); err != nil { |
||||
return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) |
||||
} |
||||
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath()) |
||||
if err != nil { |
||||
log.Error(4, "OpenRepository: %v", err) |
||||
return nil |
||||
} |
||||
commit, err := gitRepo.GetBranchCommit(opts.NewBranch) |
||||
if err != nil { |
||||
log.Error(4, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err) |
||||
return nil |
||||
} |
||||
|
||||
// Simulate push event.
|
||||
pushCommits := &PushCommits{ |
||||
Len: 1, |
||||
Commits: []*PushCommit{CommitToPushCommit(commit)}, |
||||
} |
||||
if err := CommitRepoAction(CommitRepoActionOptions{ |
||||
PusherName: doer.Name, |
||||
RepoOwnerID: repo.MustOwner().ID, |
||||
RepoName: repo.Name, |
||||
RefFullName: git.BRANCH_PREFIX + opts.NewBranch, |
||||
OldCommitID: opts.LastCommitID, |
||||
NewCommitID: commit.ID.String(), |
||||
Commits: pushCommits, |
||||
}); err != nil { |
||||
log.Error(4, "CommitRepoAction: %v", err) |
||||
return nil |
||||
} |
||||
|
||||
return DeleteUploads(uploads...) |
||||
} |
@ -0,0 +1,244 @@
|
||||
// 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" |
||||
"gopkg.in/ini.v1" |
||||
|
||||
"github.com/gogits/gogs/modules/log" |
||||
"github.com/gogits/gogs/modules/process" |
||||
"github.com/gogits/gogs/modules/setting" |
||||
"github.com/gogits/gogs/modules/sync" |
||||
) |
||||
|
||||
var MirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength) |
||||
|
||||
// Mirror represents mirror information of a repository.
|
||||
type Mirror struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
RepoID int64 |
||||
Repo *Repository `xorm:"-"` |
||||
Interval int // Hour.
|
||||
EnablePrune bool `xorm:"NOT NULL DEFAULT true"` |
||||
|
||||
Updated time.Time `xorm:"-"` |
||||
UpdatedUnix int64 |
||||
NextUpdate time.Time `xorm:"-"` |
||||
NextUpdateUnix int64 |
||||
|
||||
address string `xorm:"-"` |
||||
} |
||||
|
||||
func (m *Mirror) BeforeInsert() { |
||||
m.UpdatedUnix = time.Now().Unix() |
||||
m.NextUpdateUnix = m.NextUpdate.Unix() |
||||
} |
||||
|
||||
func (m *Mirror) BeforeUpdate() { |
||||
m.UpdatedUnix = time.Now().Unix() |
||||
m.NextUpdateUnix = m.NextUpdate.Unix() |
||||
} |
||||
|
||||
func (m *Mirror) AfterSet(colName string, _ xorm.Cell) { |
||||
var err error |
||||
switch colName { |
||||
case "repo_id": |
||||
m.Repo, err = GetRepositoryByID(m.RepoID) |
||||
if err != nil { |
||||
log.Error(3, "GetRepositoryByID[%d]: %v", m.ID, err) |
||||
} |
||||
case "updated_unix": |
||||
m.Updated = time.Unix(m.UpdatedUnix, 0).Local() |
||||
case "next_updated_unix": |
||||
m.NextUpdate = time.Unix(m.NextUpdateUnix, 0).Local() |
||||
} |
||||
} |
||||
|
||||
// ScheduleNextUpdate calculates and sets next update time.
|
||||
func (m *Mirror) ScheduleNextUpdate() { |
||||
m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour) |
||||
} |
||||
|
||||
func (m *Mirror) readAddress() { |
||||
if len(m.address) > 0 { |
||||
return |
||||
} |
||||
|
||||
cfg, err := ini.Load(m.Repo.GitConfigPath()) |
||||
if err != nil { |
||||
log.Error(4, "Load: %v", err) |
||||
return |
||||
} |
||||
m.address = cfg.Section("remote \"origin\"").Key("url").Value() |
||||
} |
||||
|
||||
// HandleCloneUserCredentials replaces user credentials from HTTP/HTTPS URL
|
||||
// with placeholder <credentials>.
|
||||
// It will fail for any other forms of clone addresses.
|
||||
func HandleCloneUserCredentials(url string, mosaics bool) string { |
||||
i := strings.Index(url, "@") |
||||
if i == -1 { |
||||
return url |
||||
} |
||||
start := strings.Index(url, "://") |
||||
if start == -1 { |
||||
return url |
||||
} |
||||
if mosaics { |
||||
return url[:start+3] + "<credentials>" + url[i:] |
||||
} |
||||
return url[:start+3] + url[i+1:] |
||||
} |
||||
|
||||
// Address returns mirror address from Git repository config without credentials.
|
||||
func (m *Mirror) Address() string { |
||||
m.readAddress() |
||||
return HandleCloneUserCredentials(m.address, false) |
||||
} |
||||
|
||||
// FullAddress returns mirror address from Git repository config.
|
||||
func (m *Mirror) FullAddress() string { |
||||
m.readAddress() |
||||
return m.address |
||||
} |
||||
|
||||
// SaveAddress writes new address to Git repository config.
|
||||
func (m *Mirror) SaveAddress(addr string) error { |
||||
configPath := m.Repo.GitConfigPath() |
||||
cfg, err := ini.Load(configPath) |
||||
if err != nil { |
||||
return fmt.Errorf("Load: %v", err) |
||||
} |
||||
|
||||
cfg.Section("remote \"origin\"").Key("url").SetValue(addr) |
||||
return cfg.SaveToIndent(configPath, "\t") |
||||
} |
||||
|
||||
// runSync returns true if sync finished without error.
|
||||
func (m *Mirror) runSync() bool { |
||||
repoPath := m.Repo.RepoPath() |
||||
wikiPath := m.Repo.WikiPath() |
||||
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second |
||||
|
||||
gitArgs := []string{"remote", "update"} |
||||
if m.EnablePrune { |
||||
gitArgs = append(gitArgs, "--prune") |
||||
} |
||||
|
||||
if _, stderr, err := process.ExecDir( |
||||
timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), |
||||
"git", gitArgs...); err != nil { |
||||
desc := fmt.Sprintf("Fail to update mirror repository '%s': %s", repoPath, stderr) |
||||
log.Error(4, desc) |
||||
if err = CreateRepositoryNotice(desc); err != nil { |
||||
log.Error(4, "CreateRepositoryNotice: %v", err) |
||||
} |
||||
return false |
||||
} |
||||
if m.Repo.HasWiki() { |
||||
if _, stderr, err := process.ExecDir( |
||||
timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath), |
||||
"git", "remote", "update", "--prune"); err != nil { |
||||
desc := fmt.Sprintf("Fail to update mirror wiki repository '%s': %s", wikiPath, stderr) |
||||
log.Error(4, desc) |
||||
if err = CreateRepositoryNotice(desc); err != nil { |
||||
log.Error(4, "CreateRepositoryNotice: %v", err) |
||||
} |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { |
||||
m := &Mirror{RepoID: repoID} |
||||
has, err := e.Get(m) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrMirrorNotExist |
||||
} |
||||
return m, nil |
||||
} |
||||
|
||||
// GetMirrorByRepoID returns mirror information of a repository.
|
||||
func GetMirrorByRepoID(repoID int64) (*Mirror, error) { |
||||
return getMirrorByRepoID(x, repoID) |
||||
} |
||||
|
||||
func updateMirror(e Engine, m *Mirror) error { |
||||
_, err := e.Id(m.ID).AllCols().Update(m) |
||||
return err |
||||
} |
||||
|
||||
func UpdateMirror(m *Mirror) error { |
||||
return updateMirror(x, m) |
||||
} |
||||
|
||||
func DeleteMirrorByRepoID(repoID int64) error { |
||||
_, err := x.Delete(&Mirror{RepoID: repoID}) |
||||
return err |
||||
} |
||||
|
||||
// MirrorUpdate checks and updates mirror repositories.
|
||||
func MirrorUpdate() { |
||||
if taskStatusTable.IsRunning(_MIRROR_UPDATE) { |
||||
return |
||||
} |
||||
taskStatusTable.Start(_MIRROR_UPDATE) |
||||
defer taskStatusTable.Stop(_MIRROR_UPDATE) |
||||
|
||||
log.Trace("Doing: MirrorUpdate") |
||||
|
||||
if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean interface{}) error { |
||||
m := bean.(*Mirror) |
||||
if m.Repo == nil { |
||||
log.Error(4, "Disconnected mirror repository found: %d", m.ID) |
||||
return nil |
||||
} |
||||
|
||||
MirrorQueue.Add(m.RepoID) |
||||
return nil |
||||
}); err != nil { |
||||
log.Error(4, "MirrorUpdate: %v", err) |
||||
} |
||||
} |
||||
|
||||
// SyncMirrors checks and syncs mirrors.
|
||||
// TODO: sync more mirrors at same time.
|
||||
func SyncMirrors() { |
||||
// Start listening on new sync requests.
|
||||
for repoID := range MirrorQueue.Queue() { |
||||
log.Trace("SyncMirrors [repo_id: %v]", repoID) |
||||
MirrorQueue.Remove(repoID) |
||||
|
||||
m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) |
||||
if err != nil { |
||||
log.Error(4, "GetMirrorByRepoID [%d]: %v", repoID, err) |
||||
continue |
||||
} |
||||
|
||||
if !m.runSync() { |
||||
continue |
||||
} |
||||
|
||||
m.ScheduleNextUpdate() |
||||
if err = UpdateMirror(m); err != nil { |
||||
log.Error(4, "UpdateMirror [%d]: %v", repoID, err) |
||||
continue |
||||
} |
||||
} |
||||
} |
||||
|
||||
func InitSyncMirrors() { |
||||
go SyncMirrors() |
||||
} |
@ -0,0 +1,62 @@
|
||||
package models_test |
||||
|
||||
import ( |
||||
. "github.com/gogits/gogs/models" |
||||
. "github.com/smartystreets/goconvey/convey" |
||||
"testing" |
||||
|
||||
"github.com/gogits/gogs/modules/markdown" |
||||
) |
||||
|
||||
func TestRepo(t *testing.T) { |
||||
Convey("The metas map", t, func() { |
||||
var repo = new(Repository) |
||||
repo.Name = "testrepo" |
||||
repo.Owner = new(User) |
||||
repo.Owner.Name = "testuser" |
||||
repo.ExternalTrackerFormat = "https://someurl.com/{user}/{repo}/{issue}" |
||||
|
||||
Convey("When no external tracker is configured", func() { |
||||
Convey("It should be nil", func() { |
||||
repo.EnableExternalTracker = false |
||||
So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil)) |
||||
}) |
||||
Convey("It should be nil even if other settings are present", func() { |
||||
repo.EnableExternalTracker = false |
||||
repo.ExternalTrackerFormat = "http://someurl.com/{user}/{repo}/{issue}" |
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC |
||||
So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil)) |
||||
}) |
||||
}) |
||||
|
||||
Convey("When an external issue tracker is configured", func() { |
||||
repo.EnableExternalTracker = true |
||||
Convey("It should default to numeric issue style", func() { |
||||
metas := repo.ComposeMetas() |
||||
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_NUMERIC) |
||||
}) |
||||
Convey("It should pass through numeric issue style setting", func() { |
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC |
||||
metas := repo.ComposeMetas() |
||||
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_NUMERIC) |
||||
}) |
||||
Convey("It should pass through alphanumeric issue style setting", func() { |
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_ALPHANUMERIC |
||||
metas := repo.ComposeMetas() |
||||
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_ALPHANUMERIC) |
||||
}) |
||||
Convey("It should contain the user name", func() { |
||||
metas := repo.ComposeMetas() |
||||
So(metas["user"], ShouldEqual, "testuser") |
||||
}) |
||||
Convey("It should contain the repo name", func() { |
||||
metas := repo.ComposeMetas() |
||||
So(metas["repo"], ShouldEqual, "testrepo") |
||||
}) |
||||
Convey("It should contain the URL format", func() { |
||||
metas := repo.ComposeMetas() |
||||
So(metas["format"], ShouldEqual, "https://someurl.com/{user}/{repo}/{issue}") |
||||
}) |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,198 @@
|
||||
// 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" |
||||
) |
||||
|
||||
// EmailAdresses is the list of all email addresses of a user. Can contain the
|
||||
// primary email address, but is not obligatory.
|
||||
type EmailAddress struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
UID int64 `xorm:"INDEX NOT NULL"` |
||||
Email string `xorm:"UNIQUE NOT NULL"` |
||||
IsActivated bool |
||||
IsPrimary bool `xorm:"-"` |
||||
} |
||||
|
||||
// GetEmailAddresses returns all email addresses belongs to given user.
|
||||
func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { |
||||
emails := make([]*EmailAddress, 0, 5) |
||||
if err := x.Where("uid=?", uid).Find(&emails); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
u, err := GetUserByID(uid) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
isPrimaryFound := false |
||||
for _, email := range emails { |
||||
if email.Email == u.Email { |
||||
isPrimaryFound = true |
||||
email.IsPrimary = true |
||||
} else { |
||||
email.IsPrimary = false |
||||
} |
||||
} |
||||
|
||||
// We alway want the primary email address displayed, even if it's not in
|
||||
// the emailaddress table (yet).
|
||||
if !isPrimaryFound { |
||||
emails = append(emails, &EmailAddress{ |
||||
Email: u.Email, |
||||
IsActivated: true, |
||||
IsPrimary: true, |
||||
}) |
||||
} |
||||
return emails, nil |
||||
} |
||||
|
||||
func isEmailUsed(e Engine, email string) (bool, error) { |
||||
if len(email) == 0 { |
||||
return true, nil |
||||
} |
||||
|
||||
return e.Get(&EmailAddress{Email: email}) |
||||
} |
||||
|
||||
// IsEmailUsed returns true if the email has been used.
|
||||
func IsEmailUsed(email string) (bool, error) { |
||||
return isEmailUsed(x, email) |
||||
} |
||||
|
||||
func addEmailAddress(e Engine, email *EmailAddress) error { |
||||
email.Email = strings.ToLower(strings.TrimSpace(email.Email)) |
||||
used, err := isEmailUsed(e, email.Email) |
||||
if err != nil { |
||||
return err |
||||
} else if used { |
||||
return ErrEmailAlreadyUsed{email.Email} |
||||
} |
||||
|
||||
_, err = e.Insert(email) |
||||
return err |
||||
} |
||||
|
||||
func AddEmailAddress(email *EmailAddress) error { |
||||
return addEmailAddress(x, email) |
||||
} |
||||
|
||||
func AddEmailAddresses(emails []*EmailAddress) error { |
||||
if len(emails) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
// Check if any of them has been used
|
||||
for i := range emails { |
||||
emails[i].Email = strings.ToLower(strings.TrimSpace(emails[i].Email)) |
||||
used, err := IsEmailUsed(emails[i].Email) |
||||
if err != nil { |
||||
return err |
||||
} else if used { |
||||
return ErrEmailAlreadyUsed{emails[i].Email} |
||||
} |
||||
} |
||||
|
||||
if _, err := x.Insert(emails); err != nil { |
||||
return fmt.Errorf("Insert: %v", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (email *EmailAddress) Activate() error { |
||||
user, err := GetUserByID(email.UID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
user.Rands = GetUserSalt() |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
email.IsActivated = true |
||||
if _, err := sess.Id(email.ID).AllCols().Update(email); err != nil { |
||||
return err |
||||
} else if err = updateUser(sess, user); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
func DeleteEmailAddress(email *EmailAddress) (err error) { |
||||
if email.ID > 0 { |
||||
_, err = x.Id(email.ID).Delete(new(EmailAddress)) |
||||
} else { |
||||
_, err = x.Where("email=?", email.Email).Delete(new(EmailAddress)) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func DeleteEmailAddresses(emails []*EmailAddress) (err error) { |
||||
for i := range emails { |
||||
if err = DeleteEmailAddress(emails[i]); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func MakeEmailPrimary(email *EmailAddress) error { |
||||
has, err := x.Get(email) |
||||
if err != nil { |
||||
return err |
||||
} else if !has { |
||||
return ErrEmailNotExist |
||||
} |
||||
|
||||
if !email.IsActivated { |
||||
return ErrEmailNotActivated |
||||
} |
||||
|
||||
user := &User{ID: email.UID} |
||||
has, err = x.Get(user) |
||||
if err != nil { |
||||
return err |
||||
} else if !has { |
||||
return ErrUserNotExist{email.UID, ""} |
||||
} |
||||
|
||||
// Make sure the former primary email doesn't disappear.
|
||||
formerPrimaryEmail := &EmailAddress{Email: user.Email} |
||||
has, err = x.Get(formerPrimaryEmail) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if !has { |
||||
formerPrimaryEmail.UID = user.ID |
||||
formerPrimaryEmail.IsActivated = user.IsActive |
||||
if _, err = sess.Insert(formerPrimaryEmail); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
user.Email = email.Email |
||||
if _, err = sess.Id(user.ID).AllCols().Update(user); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue