diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/access.go | 28 | ||||
-rw-r--r-- | models/action.go | 17 | ||||
-rw-r--r-- | models/git_diff.go | 212 | ||||
-rw-r--r-- | models/models.go | 74 | ||||
-rw-r--r-- | models/models_sqlite.go | 15 | ||||
-rw-r--r-- | models/oauth2.go | 70 | ||||
-rw-r--r-- | models/publickey.go | 4 | ||||
-rw-r--r-- | models/release.go | 83 | ||||
-rw-r--r-- | models/repo.go | 302 | ||||
-rw-r--r-- | models/update.go | 84 | ||||
-rw-r--r-- | models/user.go | 95 |
11 files changed, 878 insertions, 106 deletions
diff --git a/models/access.go b/models/access.go index 83261575..970f4a94 100644 --- a/models/access.go +++ b/models/access.go @@ -7,6 +7,8 @@ package models import ( "strings" "time" + + "github.com/go-xorm/xorm" ) // Access types. @@ -19,7 +21,7 @@ const ( type Access struct { Id int64 UserName string `xorm:"unique(s)"` - RepoName string `xorm:"unique(s)"` + RepoName string `xorm:"unique(s)"` // <user name>/<repo name> Mode int `xorm:"unique(s)"` Created time.Time `xorm:"created"` } @@ -40,12 +42,28 @@ func UpdateAccess(access *Access) error { return err } +// UpdateAccess updates access information with session for rolling back. +func UpdateAccessWithSession(sess *xorm.Session, access *Access) error { + if _, err := sess.Id(access.Id).Update(access); err != nil { + sess.Rollback() + return err + } + return nil +} + // HasAccess returns true if someone can read or write to given repository. func HasAccess(userName, repoName string, mode int) (bool, error) { - return orm.Get(&Access{ - Id: 0, + access := &Access{ UserName: strings.ToLower(userName), RepoName: strings.ToLower(repoName), - Mode: mode, - }) + } + has, err := orm.Get(access) + if err != nil { + return false, err + } else if !has { + return false, nil + } else if mode > access.Mode { + return false, nil + } + return true, nil } diff --git a/models/action.go b/models/action.go index a642a82c..a9a41a9f 100644 --- a/models/action.go +++ b/models/action.go @@ -6,8 +6,11 @@ package models import ( "encoding/json" + "strings" "time" + "github.com/gogits/git" + "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -22,6 +25,7 @@ const ( OP_CREATE_ISSUE OP_PULL_REQUEST OP_TRANSFER_REPO + OP_PUSH_TAG ) // Action represents user operation type and other information to repository., @@ -67,7 +71,16 @@ func (a Action) GetContent() string { // CommitRepoAction adds new action for committing repository. func CommitRepoAction(userId int64, userName, actEmail string, repoId int64, repoName string, refName string, commit *base.PushCommits) error { - log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName) + // log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName) + + opType := OP_COMMIT_REPO + // Check it's tag push or branch. + if strings.HasPrefix(refName, "refs/tags/") { + opType = OP_PUSH_TAG + commit = &base.PushCommits{} + } + + refName = git.RefEndName(refName) bs, err := json.Marshal(commit) if err != nil { @@ -76,7 +89,7 @@ func CommitRepoAction(userId int64, userName, actEmail string, } if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail, - OpType: OP_COMMIT_REPO, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil { + OpType: opType, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil { log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName) return err } diff --git a/models/git_diff.go b/models/git_diff.go new file mode 100644 index 00000000..cf93af69 --- /dev/null +++ b/models/git_diff.go @@ -0,0 +1,212 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "bufio" + "io" + "os" + "os/exec" + "strings" + + "github.com/gogits/git" + + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/log" +) + +// Diff line types. +const ( + DIFF_LINE_PLAIN = iota + 1 + DIFF_LINE_ADD + DIFF_LINE_DEL + DIFF_LINE_SECTION +) + +const ( + DIFF_FILE_ADD = iota + 1 + DIFF_FILE_CHANGE + DIFF_FILE_DEL +) + +type DiffLine struct { + LeftIdx int + RightIdx int + Type int + Content string +} + +func (d DiffLine) GetType() int { + return d.Type +} + +type DiffSection struct { + Name string + Lines []*DiffLine +} + +type DiffFile struct { + Name string + Addition, Deletion int + Type int + IsBin bool + Sections []*DiffSection +} + +type Diff struct { + TotalAddition, TotalDeletion int + Files []*DiffFile +} + +func (diff *Diff) NumFiles() int { + return len(diff.Files) +} + +const DIFF_HEAD = "diff --git " + +func ParsePatch(reader io.Reader) (*Diff, error) { + scanner := bufio.NewScanner(reader) + var ( + curFile *DiffFile + curSection = &DiffSection{ + Lines: make([]*DiffLine, 0, 10), + } + + leftLine, rightLine int + ) + + diff := &Diff{Files: make([]*DiffFile, 0)} + var i int + for scanner.Scan() { + line := scanner.Text() + // fmt.Println(i, line) + if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") { + continue + } + + i = i + 1 + + // Diff data too large. + if i == 5000 { + log.Warn("Diff data too large") + return &Diff{}, nil + } + + if line == "" { + continue + } + + switch { + case line[0] == ' ': + diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine} + leftLine++ + rightLine++ + curSection.Lines = append(curSection.Lines, diffLine) + continue + case line[0] == '@': + curSection = &DiffSection{} + curFile.Sections = append(curFile.Sections, curSection) + ss := strings.Split(line, "@@") + diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line} + curSection.Lines = append(curSection.Lines, diffLine) + + // Parse line number. + ranges := strings.Split(ss[len(ss)-2][1:], " ") + leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int() + rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int() + continue + case line[0] == '+': + curFile.Addition++ + diff.TotalAddition++ + diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine} + rightLine++ + curSection.Lines = append(curSection.Lines, diffLine) + continue + case line[0] == '-': + curFile.Deletion++ + diff.TotalDeletion++ + diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine} + if leftLine > 0 { + leftLine++ + } + curSection.Lines = append(curSection.Lines, diffLine) + case strings.HasPrefix(line, "Binary"): + curFile.IsBin = true + continue + } + + // Get new file. + if strings.HasPrefix(line, DIFF_HEAD) { + fs := strings.Split(line[len(DIFF_HEAD):], " ") + a := fs[0] + + curFile = &DiffFile{ + Name: a[strings.Index(a, "/")+1:], + Type: DIFF_FILE_CHANGE, + Sections: make([]*DiffSection, 0, 10), + } + diff.Files = append(diff.Files, curFile) + + // Check file diff type. + for scanner.Scan() { + switch { + case strings.HasPrefix(scanner.Text(), "new file"): + curFile.Type = DIFF_FILE_ADD + case strings.HasPrefix(scanner.Text(), "deleted"): + curFile.Type = DIFF_FILE_DEL + case strings.HasPrefix(scanner.Text(), "index"): + curFile.Type = DIFF_FILE_CHANGE + } + if curFile.Type > 0 { + break + } + } + } + } + + return diff, nil +} + +func GetDiff(repoPath, commitid string) (*Diff, error) { + repo, err := git.OpenRepository(repoPath) + if err != nil { + return nil, err + } + + commit, err := repo.GetCommit(commitid) + if err != nil { + return nil, err + } + + // First commit of repository. + if commit.ParentCount() == 0 { + rd, wr := io.Pipe() + go func() { + cmd := exec.Command("git", "show", commitid) + cmd.Dir = repoPath + cmd.Stdout = wr + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Run() + wr.Close() + }() + defer rd.Close() + return ParsePatch(rd) + } + + rd, wr := io.Pipe() + go func() { + c, _ := commit.Parent(0) + cmd := exec.Command("git", "diff", c.Id.String(), commitid) + cmd.Dir = repoPath + cmd.Stdout = wr + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Run() + wr.Close() + }() + defer rd.Close() + return ParsePatch(rd) +} diff --git a/models/models.go b/models/models.go index 0ad86337..8e8835ab 100644 --- a/models/models.go +++ b/models/models.go @@ -8,26 +8,35 @@ import ( "fmt" "os" "path" + "strings" _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/xorm" _ "github.com/lib/pq" - "github.com/lunny/xorm" - // _ "github.com/mattn/go-sqlite3" "github.com/gogits/gogs/modules/base" ) var ( - orm *xorm.Engine + orm *xorm.Engine + tables []interface{} + HasEngine bool DbCfg struct { Type, Host, Name, User, Pwd, Path, SslMode string } - UseSQLite3 bool + EnableSQLite3 bool + UseSQLite3 bool ) +func init() { + tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch), + new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow), + new(Mirror), new(Release)) +} + func LoadModelsConfig() { DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE") if DbCfg.Type == "sqlite3" { @@ -47,20 +56,31 @@ func NewTestEngine(x *xorm.Engine) (err error) { x, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name)) case "postgres": - x, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s", - DbCfg.User, DbCfg.Pwd, DbCfg.Name, DbCfg.SslMode)) - // case "sqlite3": - // os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm) - // x, err = xorm.NewEngine("sqlite3", DbCfg.Path) + var host, port = "127.0.0.1", "5432" + fields := strings.Split(DbCfg.Host, ":") + if len(fields) > 0 { + host = fields[0] + } + if len(fields) > 1 { + port = fields[1] + } + cnnstr := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s", + DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode) + //fmt.Println(cnnstr) + x, err = xorm.NewEngine("postgres", cnnstr) + case "sqlite3": + if !EnableSQLite3 { + return fmt.Errorf("Unknown database type: %s", DbCfg.Type) + } + os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm) + x, err = xorm.NewEngine("sqlite3", DbCfg.Path) default: return fmt.Errorf("Unknown database type: %s", DbCfg.Type) } if err != nil { return fmt.Errorf("models.init(fail to conntect database): %v", err) } - - return x.Sync(new(User), new(PublicKey), new(Repository), new(Watch), - new(Action), new(Access), new(Issue), new(Comment)) + return x.Sync(tables...) } func SetEngine() (err error) { @@ -69,8 +89,16 @@ func SetEngine() (err error) { orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name)) case "postgres": - orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s", - DbCfg.User, DbCfg.Pwd, DbCfg.Name, DbCfg.SslMode)) + var host, port = "127.0.0.1", "5432" + fields := strings.Split(DbCfg.Host, ":") + if len(fields) > 0 { + host = fields[0] + } + if len(fields) > 1 { + port = fields[1] + } + orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s", + DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode)) case "sqlite3": os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm) orm, err = xorm.NewEngine("sqlite3", DbCfg.Path) @@ -91,7 +119,7 @@ func SetEngine() (err error) { if err != nil { return fmt.Errorf("models.init(fail to create xorm.log): %v", err) } - orm.Logger = f + orm.Logger = xorm.NewSimpleLogger(f) orm.ShowSQL = true orm.ShowDebug = true @@ -102,16 +130,19 @@ func SetEngine() (err error) { func NewEngine() (err error) { if err = SetEngine(); err != nil { return err - } else if err = orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch), - new(Action), new(Access), new(Issue), new(Comment)); err != nil { - return fmt.Errorf("sync database struct error: %v", err) + } + if err = orm.Sync(tables...); err != nil { + return fmt.Errorf("sync database struct error: %v\n", err) } return nil } type Statistic struct { Counter struct { - User, PublicKey, Repo, Watch, Action, Access int64 + User, PublicKey, Repo, + Watch, Action, Access, + Issue, Comment, + Mirror, Oauth, Release int64 } } @@ -122,5 +153,10 @@ func GetStatistic() (stats Statistic) { stats.Counter.Watch, _ = orm.Count(new(Watch)) stats.Counter.Action, _ = orm.Count(new(Action)) stats.Counter.Access, _ = orm.Count(new(Access)) + stats.Counter.Issue, _ = orm.Count(new(Issue)) + stats.Counter.Comment, _ = orm.Count(new(Comment)) + stats.Counter.Mirror, _ = orm.Count(new(Mirror)) + stats.Counter.Oauth, _ = orm.Count(new(Oauth2)) + stats.Counter.Release, _ = orm.Count(new(Release)) return } diff --git a/models/models_sqlite.go b/models/models_sqlite.go new file mode 100644 index 00000000..c77e5ae5 --- /dev/null +++ b/models/models_sqlite.go @@ -0,0 +1,15 @@ +// +build sqlite + +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + _ "github.com/mattn/go-sqlite3" +) + +func init() { + EnableSQLite3 = true +} diff --git a/models/oauth2.go b/models/oauth2.go index 70dcd510..d1ae4611 100644 --- a/models/oauth2.go +++ b/models/oauth2.go @@ -1,18 +1,76 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package models -import "time" +import ( + "errors" +) // OT: Oauth2 Type const ( OT_GITHUB = iota + 1 OT_GOOGLE OT_TWITTER + OT_QQ + OT_WEIBO + OT_BITBUCKET + OT_OSCHINA + OT_FACEBOOK +) + +var ( + ErrOauth2RecordNotExist = errors.New("OAuth2 record does not exist") + ErrOauth2NotAssociated = errors.New("OAuth2 is not associated with user") ) type Oauth2 struct { - Uid int64 `xorm:"pk"` // userId - Type int `xorm:"pk unique(oauth)"` // twitter,github,google... - Identity string `xorm:"pk unique(oauth)"` // id.. - Token string `xorm:"VARCHAR(200) not null"` - RefreshTime time.Time `xorm:"created"` + Id int64 + Uid int64 `xorm:"unique(s)"` // userId + User *User `xorm:"-"` + Type int `xorm:"unique(s) unique(oauth)"` // twitter,github,google... + Identity string `xorm:"unique(s) unique(oauth)"` // id.. + Token string `xorm:"TEXT not null"` +} + +func BindUserOauth2(userId, oauthId int64) error { + _, err := orm.Id(oauthId).Update(&Oauth2{Uid: userId}) + return err +} + +func AddOauth2(oa *Oauth2) error { + _, err := orm.Insert(oa) + return err +} + +func GetOauth2(identity string) (oa *Oauth2, err error) { + oa = &Oauth2{Identity: identity} + isExist, err := orm.Get(oa) + if err != nil { + return + } else if !isExist { + return nil, ErrOauth2RecordNotExist + } else if oa.Uid == -1 { + return oa, ErrOauth2NotAssociated + } + oa.User, err = GetUserById(oa.Uid) + return oa, err +} + +func GetOauth2ById(id int64) (oa *Oauth2, err error) { + oa = new(Oauth2) + has, err := orm.Id(id).Get(oa) + if err != nil { + return nil, err + } else if !has { + return nil, ErrOauth2RecordNotExist + } + return oa, nil +} + +// GetOauthByUserId returns list of oauthes that are releated to given user. +func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) { + err = orm.Find(&oas, Oauth2{Uid: uid}) + return oas, err } diff --git a/models/publickey.go b/models/publickey.go index 42d2523b..ed47ff20 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -77,8 +77,8 @@ func init() { // PublicKey represents a SSH key of user. type PublicKey struct { Id int64 - OwnerId int64 `xorm:" index not null"` - Name string `xorm:" not null"` //UNIQUE(s) + OwnerId int64 `xorm:"unique(s) index not null"` + Name string `xorm:"unique(s) not null"` Fingerprint string Content string `xorm:"TEXT not null"` Created time.Time `xorm:"created"` diff --git a/models/release.go b/models/release.go new file mode 100644 index 00000000..1df62720 --- /dev/null +++ b/models/release.go @@ -0,0 +1,83 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "errors" + "strings" + "time" + + "github.com/Unknwon/com" + "github.com/gogits/git" +) + +var ( + ErrReleaseAlreadyExist = errors.New("Release already exist") +) + +// Release represents a release of repository. +type Release struct { + Id int64 + RepoId int64 + PublisherId int64 + Publisher *User `xorm:"-"` + Title string + TagName string + LowerTagName string + SHA1 string + NumCommits int + NumCommitsBehind int `xorm:"-"` + Note string `xorm:"TEXT"` + IsPrerelease bool + Created time.Time `xorm:"created"` +} + +// GetReleasesByRepoId returns a list of releases of repository. +func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) { + err = orm.Desc("created").Find(&rels, Release{RepoId: repoId}) + return rels, err +} + +// IsReleaseExist returns true if release with given tag name already exists. +func IsReleaseExist(repoId int64, tagName string) (bool, error) { + if len(tagName) == 0 { + return false, nil + } + + return orm.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}) +} + +// CreateRelease creates a new release of repository. +func CreateRelease(repoPath string, rel *Release, gitRepo *git.Repository) error { + isExist, err := IsReleaseExist(rel.RepoId, rel.TagName) + if err != nil { + return err + } else if isExist { + return ErrReleaseAlreadyExist + } + + if !git.IsTagExist(repoPath, rel.TagName) { + _, stderr, err := com.ExecCmdDir(repoPath, "git", "tag", rel.TagName, "-m", rel.Title) + if err != nil { + return err + } else if strings.Contains(stderr, "fatal:") { + return errors.New(stderr) + } + } else { + commit, err := gitRepo.GetCommitOfTag(rel.TagName) + if err != nil { + return err + } + + rel.NumCommits, err = commit.CommitsCount() + if err != nil { + return err + } + } + + rel.LowerTagName = strings.ToLower(rel.TagName) + _, err = orm.InsertOne(rel) + return err +} diff --git a/models/repo.go b/models/repo.go index e8ebce92..19fe6e28 100644 --- a/models/repo.go +++ b/models/repo.go @@ -30,7 +30,8 @@ var ( ErrRepoNotExist = errors.New("Repository does not exist") ErrRepoFileNotExist = errors.New("Target Repo file does not exist") ErrRepoNameIllegal = errors.New("Repository name contains illegal characters") - ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded") + ErrRepoFileNotLoaded = errors.New("repo file not loaded") + ErrMirrorNotExist = errors.New("Mirror does not exist") ) var ( @@ -65,6 +66,7 @@ func NewRepoContext() { type Repository struct { Id int64 OwnerId int64 `xorm:"unique(s)"` + Owner *User `xorm:"-"` ForkId int64 LowerName string `xorm:"unique(s) index not null"` Name string `xorm:"index not null"` @@ -74,11 +76,14 @@ type Repository struct { NumStars int NumForks int NumIssues int - NumReleases int `xorm:"NOT NULL"` NumClosedIssues int NumOpenIssues int `xorm:"-"` + NumTags int `xorm:"-"` IsPrivate bool + IsMirror bool IsBare bool + IsGoget bool + DefaultBranch string Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } @@ -117,13 +122,133 @@ func IsLegalName(repoName string) bool { return true } +// Mirror represents a mirror information of repository. +type Mirror struct { + Id int64 + RepoId int64 + RepoName string // <user name>/<repo name> + Interval int // Hour. + Updated time.Time `xorm:"UPDATED"` + NextUpdate time.Time +} + +func GetMirror(repoId int64) (*Mirror, error) { + m := &Mirror{RepoId: repoId} + has, err := orm.Get(m) + if err != nil { + return nil, err + } else if !has { + return nil, ErrMirrorNotExist + } + return m, nil +} + +func UpdateMirror(m *Mirror) error { + _, err := orm.Id(m.Id).Update(m) + return err +} + +// MirrorUpdate checks and updates mirror repositories. +func MirrorUpdate() { + if err := orm.Iterate(new(Mirror), func(idx int, bean interface{}) error { + m := bean.(*Mirror) + if m.NextUpdate.After(time.Now()) { + return nil + } + + repoPath := filepath.Join(base.RepoRootPath, m.RepoName+".git") + _, stderr, err := com.ExecCmdDir(repoPath, "git", "remote", "update") + if err != nil { + return err + } else if strings.Contains(stderr, "fatal:") { + return errors.New(stderr) + } else if err = git.UnpackRefs(repoPath); err != nil { + return err + } + + m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour) + return UpdateMirror(m) + }); err != nil { + log.Error("repo.MirrorUpdate: %v", err) + } +} + +// MirrorRepository creates a mirror repository from source. +func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error { + _, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath) + if err != nil { + return err + } else if strings.Contains(stderr, "fatal:") { + return errors.New(stderr) + } + + if _, err = orm.InsertOne(&Mirror{ + RepoId: repoId, + RepoName: strings.ToLower(userName + "/" + repoName), + Interval: 24, + NextUpdate: time.Now().Add(24 * time.Hour), + }); err != nil { + return err + } + + return git.UnpackRefs(repoPath) +} + +// MigrateRepository migrates a existing repository from other project hosting. +func MigrateRepository(user *User, name, desc string, private, mirror bool, url string) (*Repository, error) { + repo, err := CreateRepository(user, name, desc, "", "", private, mirror, false) + if err != nil { + return nil, err + } + + // Clone to temprory path and do the init commit. + tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) + os.MkdirAll(tmpDir, os.ModePerm) + + repoPath := RepoPath(user.Name, name) + + repo.IsBare = false + if mirror { + if err = MirrorRepository(repo.Id, user.Name, repo.Name, repoPath, url); err != nil { + return repo, err + } + repo.IsMirror = true + return repo, UpdateRepository(repo) + } + + // Clone from local repository. + _, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir) + if err != nil { + return repo, err + } else if strings.Contains(stderr, "fatal:") { + return repo, errors.New("git clone: " + stderr) + } + + // Pull data from source. + _, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url) + if err != nil { + return repo, err + } else if strings.Contains(stderr, "fatal:") { + return repo, errors.New("git pull: " + stderr) + } + + // Push data to local repository. + if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil { + return repo, err + } else if strings.Contains(stderr, "fatal:") { + return repo, errors.New("git push: " + stderr) + } + + return repo, UpdateRepository(repo) +} + // CreateRepository creates a repository for given user or orgnaziation. -func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) { - if !IsLegalName(repoName) { +func CreateRepository(user *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) { + if !IsLegalName(name) { return nil, ErrRepoNameIllegal } - isExist, err := IsRepositoryExist(user, repoName) + isExist, err := IsRepositoryExist(user, name) if err != nil { return nil, err } else if isExist { @@ -131,18 +256,16 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv } repo := &Repository{ - OwnerId: user.Id, - Name: repoName, - LowerName: strings.ToLower(repoName), - Description: desc, - IsPrivate: private, - IsBare: repoLang == "" && license == "" && !initReadme, + OwnerId: user.Id, + Name: name, + LowerName: strings.ToLower(name), + Description: desc, + IsPrivate: private, + IsBare: lang == "" && license == "" && !initReadme, + DefaultBranch: "master", } + repoPath := RepoPath(user.Name, repo.Name) - repoPath := RepoPath(user.Name, repoName) - if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil { - return nil, err - } sess := orm.NewSession() defer sess.Close() sess.Begin() @@ -151,23 +274,27 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(repo): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(1): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(1): %v", user.Name, repo.Name, err2)) } sess.Rollback() return nil, err } + mode := AU_WRITABLE + if mirror { + mode = AU_READABLE + } access := Access{ UserName: user.LowerName, RepoName: strings.ToLower(path.Join(user.Name, repo.Name)), - Mode: AU_WRITABLE, + Mode: mode, } if _, err = sess.Insert(&access); err != nil { sess.Rollback() if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(access): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(2): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(2): %v", user.Name, repo.Name, err2)) } return nil, err } @@ -178,7 +305,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(repo count): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2)) } return nil, err } @@ -188,25 +315,36 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(commit): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2)) } return nil, err } - c := exec.Command("git", "update-server-info") - c.Dir = repoPath - if err = c.Run(); err != nil { - log.Error("repo.CreateRepository(exec update-server-info): %v", err) - } - - if err = NewRepoAction(user, repo); err != nil { - log.Error("repo.CreateRepository(NewRepoAction): %v", err) + if !repo.IsPrivate { + if err = NewRepoAction(user, repo); err != nil { + log.Error("repo.CreateRepository(NewRepoAction): %v", err) + } } if err = WatchRepo(user.Id, repo.Id, true); err != nil { log.Error("repo.CreateRepository(WatchRepo): %v", err) } + // No need for init for mirror. + if mirror { + return repo, nil + } + + if err = initRepository(repoPath, user, repo, initReadme, lang, license); err != nil { + return nil, err + } + + c := exec.Command("git", "update-server-info") + c.Dir = repoPath + if err = c.Run(); err != nil { + log.Error("repo.CreateRepository(exec update-server-info): %v", err) + } + return repo, nil } @@ -227,24 +365,21 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) { var stderr string if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "add", "--all"); err != nil { return err - } - if len(stderr) > 0 { - log.Trace("stderr(1): %s", stderr) + } else if strings.Contains(stderr, "fatal:") { + return errors.New("git add: " + stderr) } if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", "Init commit"); err != nil { return err - } - if len(stderr) > 0 { - log.Trace("stderr(2): %s", stderr) + } else if strings.Contains(stderr, "fatal:") { + return errors.New("git commit: " + stderr) } if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil { return err - } - if len(stderr) > 0 { - log.Trace("stderr(3): %s", stderr) + } else if strings.Contains(stderr, "fatal:") { + return errors.New("git push: " + stderr) } return nil } @@ -260,6 +395,13 @@ func createHookUpdate(hookPath, content string) error { return err } +// SetRepoEnvs sets environment variables for command update. +func SetRepoEnvs(userId int64, userName, repoName string) { + os.Setenv("userId", base.ToStr(userId)) + os.Setenv("userName", userName) + os.Setenv("repoName", repoName) +} + // InitRepository initializes README and .gitignore if needed. func initRepository(f string, user *User, repo *Repository, initReadme bool, repoLang, license string) error { repoPath := RepoPath(user.Name, repo.Name) @@ -271,7 +413,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep // hook/post-update if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"), - fmt.Sprintf("#!/usr/bin/env bash\n%s update $1 $2 $3\n", + fmt.Sprintf("#!/usr/bin/env %s\n%s update $1 $2 $3\n", base.ScriptType, strings.Replace(appPath, "\\", "/", -1))); err != nil { return err } @@ -292,8 +434,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) os.MkdirAll(tmpDir, os.ModePerm) - if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil { + _, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir) + if err != nil { return err + } else if strings.Contains(stderr, "fatal:") { + return errors.New("git clone: " + stderr) } // README @@ -310,7 +455,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep if repoLang != "" { filePath := "conf/gitignore/" + repoLang if com.IsFile(filePath) { - if _, err := com.Copy(filePath, + if err := com.Copy(filePath, filepath.Join(tmpDir, fileName["gitign"])); err != nil { return err } @@ -321,7 +466,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep if license != "" { filePath := "conf/license/" + license if com.IsFile(filePath) { - if _, err := com.Copy(filePath, + if err := com.Copy(filePath, filepath.Join(tmpDir, fileName["license"])); err != nil { return err } @@ -332,6 +477,8 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep return nil } + SetRepoEnvs(user.Id, user.Name, repo.Name) + // Apply changes and commit. return initRepoCommit(tmpDir, user.NewGitSig()) } @@ -365,6 +512,7 @@ func GetRepos(num, offset int) ([]UserRepo, error) { return urepos, nil } +// RepoPath returns repository path by given user and repository name. func RepoPath(userName, repoName string) string { return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") } @@ -381,45 +529,62 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil { return err } + + sess := orm.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + for i := range accesses { accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName if accesses[i].UserName == user.LowerName { accesses[i].UserName = newUser.LowerName } - if err = UpdateAccess(&accesses[i]); err != nil { + if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil { return err } } // Update repository. repo.OwnerId = newUser.Id - if _, err := orm.Id(repo.Id).Update(repo); err != nil { + if _, err := sess.Id(repo.Id).Update(repo); err != nil { + sess.Rollback() return err } // Update user repository number. rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?" - if _, err = orm.Exec(rawSql, newUser.Id); err != nil { + if _, err = sess.Exec(rawSql, newUser.Id); err != nil { + sess.Rollback() return err } rawSql = "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" - if _, err = orm.Exec(rawSql, user.Id); err != nil { + if _, err = sess.Exec(rawSql, user.Id); err != nil { + sess.Rollback() return err } // Add watch of new owner to repository. if !IsWatching(newUser.Id, repo.Id) { if err = WatchRepo(newUser.Id, repo.Id, true); err != nil { + sess.Rollback() return err } } if err = TransferRepoAction(user, newUser, repo); err != nil { + sess.Rollback() return err } // Change repository directory name. - return os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name)) + if err = os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil { + sess.Rollback() + return err + } + + return sess.Commit() } // ChangeRepositoryName changes all corresponding setting from old repository name to new one. @@ -429,15 +594,27 @@ func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil { return err } + + sess := orm.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + for i := range accesses { accesses[i].RepoName = userName + "/" + newRepoName - if err = UpdateAccess(&accesses[i]); err != nil { + if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil { return err } } // Change repository directory name. - return os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)) + if err = os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)); err != nil { + sess.Rollback() + return err + } + + return sess.Commit() } func UpdateRepository(repo *Repository) error { @@ -476,8 +653,7 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { sess.Rollback() return err } - rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" - if _, err = sess.Exec(rawSql, userId); err != nil { + if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil { sess.Rollback() return err } @@ -485,6 +661,16 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { sess.Rollback() return err } + if _, err = sess.Delete(&Mirror{RepoId: repoId}); err != nil { + sess.Rollback() + return err + } + + rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" + if _, err = sess.Exec(rawSql, userId); err != nil { + sess.Rollback() + return err + } if err = sess.Commit(); err != nil { sess.Rollback() return err @@ -525,12 +711,24 @@ func GetRepositoryById(id int64) (*Repository, error) { } // GetRepositories returns the list of repositories of given user. -func GetRepositories(user *User) ([]Repository, error) { +func GetRepositories(user *User, private bool) ([]Repository, error) { repos := make([]Repository, 0, 10) - err := orm.Desc("updated").Find(&repos, &Repository{OwnerId: user.Id}) + sess := orm.Desc("updated") + if !private { + sess.Where("is_private=?", false) + } + + err := sess.Find(&repos, &Repository{OwnerId: user.Id}) + return repos, err +} + +// GetRecentUpdatedRepositories returns the list of repositories that are recently updated. +func GetRecentUpdatedRepositories() (repos []*Repository, err error) { + err = orm.Where("is_private=?", false).Limit(5).Desc("updated").Find(&repos) return repos, err } +// GetRepositoryCount returns the total number of repositories of user. func GetRepositoryCount(user *User) (int64, error) { return orm.Count(&Repository{OwnerId: user.Id}) } diff --git a/models/update.go b/models/update.go new file mode 100644 index 00000000..2f59547b --- /dev/null +++ b/models/update.go @@ -0,0 +1,84 @@ +package models + +import ( + "container/list" + "os/exec" + "strings" + + "github.com/gogits/git" + "github.com/gogits/gogs/modules/base" + qlog "github.com/qiniu/log" +) + +func Update(refName, oldCommitId, newCommitId, userName, repoName string, userId int64) { + isNew := strings.HasPrefix(oldCommitId, "0000000") + if isNew && + strings.HasPrefix(newCommitId, "0000000") { + qlog.Fatal("old rev and new rev both 000000") + } + + f := RepoPath(userName, repoName) + + gitUpdate := exec.Command("git", "update-server-info") + gitUpdate.Dir = f + gitUpdate.Run() + + repo, err := git.OpenRepository(f) + if err != nil { + qlog.Fatalf("runUpdate.Open repoId: %v", err) + } + + newCommit, err := repo.GetCommit(newCommitId) + if err != nil { + qlog.Fatalf("runUpdate GetCommit of newCommitId: %v", err) + return + } + + var l *list.List + // if a new branch + if isNew { + l, err = newCommit.CommitsBefore() + if err != nil { + qlog.Fatalf("Find CommitsBefore erro: %v", err) + } + } else { + l, err = newCommit.CommitsBeforeUntil(oldCommitId) + if err != nil { + qlog.Fatalf("Find CommitsBeforeUntil erro: %v", err) + return + } + } + + if err != nil { + qlog.Fatalf("runUpdate.Commit repoId: %v", err) + } + + repos, err := GetRepositoryByName(userId, repoName) + if err != nil { + qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err) + } + + commits := make([]*base.PushCommit, 0) + var maxCommits = 3 + var actEmail string + for e := l.Front(); e != nil; e = e.Next() { + commit := e.Value.(*git.Commit) + if actEmail == "" { + actEmail = commit.Committer.Email + } + commits = append(commits, + &base.PushCommit{commit.Id.String(), + commit.Message(), + commit.Author.Email, + commit.Author.Name}) + if len(commits) >= maxCommits { + break + } + } + + //commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()}) + if err = CommitRepoAction(userId, userName, actEmail, + repos.Id, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil { + qlog.Fatalf("runUpdate.models.CommitRepoAction: %v", err) + } +} diff --git a/models/user.go b/models/user.go index 2641a15f..ab43df7a 100644 --- a/models/user.go +++ b/models/user.go @@ -5,6 +5,7 @@ package models import ( + "crypto/sha256" "encoding/hex" "errors" "fmt" @@ -13,8 +14,6 @@ import ( "strings" "time" - "github.com/dchest/scrypt" - "github.com/gogits/git" "github.com/gogits/gogs/modules/base" @@ -62,6 +61,7 @@ type User struct { IsActive bool IsAdmin bool Rands string `xorm:"VARCHAR(10)"` + Salt string `xorm:"VARCHAR(10)"` Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } @@ -76,7 +76,7 @@ func (user *User) AvatarLink() string { if base.Service.EnableCacheAvatar { return "/avatar/" + user.Avatar } - return "http://1.gravatar.com/avatar/" + user.Avatar + return "//1.gravatar.com/avatar/" + user.Avatar } // NewGitSig generates and returns the signature of given user. @@ -89,10 +89,9 @@ func (user *User) NewGitSig() *git.Signature { } // EncodePasswd encodes password to safe format. -func (user *User) EncodePasswd() error { - newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64) +func (user *User) EncodePasswd() { + newPasswd := base.PBKDF2([]byte(user.Passwd), []byte(user.Salt), 10000, 50, sha256.New) user.Passwd = fmt.Sprintf("%x", newPasswd) - return err } // Member represents user is member of organization. @@ -148,9 +147,9 @@ func RegisterUser(user *User) (*User, error) { user.Avatar = base.EncodeMd5(user.Email) user.AvatarEmail = user.Email user.Rands = GetUserSalt() - if err = user.EncodePasswd(); err != nil { - return nil, err - } else if _, err = orm.Insert(user); err != nil { + user.Salt = GetUserSalt() + user.EncodePasswd() + if _, err = orm.Insert(user); err != nil { return nil, err } else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil { if _, err := orm.Id(user.Id).Delete(&User{}); err != nil { @@ -218,17 +217,24 @@ func ChangeUserName(user *User, newUserName string) (err error) { if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil { return err } + + sess := orm.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + for i := range accesses { accesses[i].UserName = newUserName if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") { accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1) - if err = UpdateAccess(&accesses[i]); err != nil { + if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil { return err } } } - repos, err := GetRepositories(user) + repos, err := GetRepositories(user, true) if err != nil { return err } @@ -241,14 +247,19 @@ func ChangeUserName(user *User, newUserName string) (err error) { for j := range accesses { accesses[j].RepoName = newUserName + "/" + repos[i].LowerName - if err = UpdateAccess(&accesses[j]); err != nil { + if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil { return err } } } // Change user directory name. - return os.Rename(UserPath(user.LowerName), UserPath(newUserName)) + if err = os.Rename(UserPath(user.LowerName), UserPath(newUserName)); err != nil { + sess.Rollback() + return err + } + + return sess.Commit() } // UpdateUser updates user's information. @@ -278,11 +289,26 @@ func DeleteUser(user *User) error { // TODO: check issues, other repos' commits + // Delete all followers. + if _, err = orm.Delete(&Follow{FollowId: user.Id}); err != nil { + return err + } + + // Delete oauth2. + if _, err = orm.Delete(&Oauth2{Uid: user.Id}); err != nil { + return err + } + // Delete all feeds. if _, err = orm.Delete(&Action{UserId: user.Id}); err != nil { return err } + // Delete all watches. + if _, err = orm.Delete(&Watch{UserId: user.Id}); err != nil { + return err + } + // Delete all accesses. if _, err = orm.Delete(&Access{UserName: user.LowerName}); err != nil { return err @@ -305,7 +331,6 @@ func DeleteUser(user *User) error { } _, err = orm.Delete(user) - // TODO: delete and update follower information. return err } @@ -355,20 +380,50 @@ func GetUserByName(name string) (*User, error) { return user, nil } -// LoginUserPlain validates user by raw user name and password. -func LoginUserPlain(name, passwd string) (*User, error) { - user := User{LowerName: strings.ToLower(name), Passwd: passwd} - if err := user.EncodePasswd(); err != nil { +// GetUserEmailsByNames returns a slice of e-mails corresponds to names. +func GetUserEmailsByNames(names []string) []string { + mails := make([]string, 0, len(names)) + for _, name := range names { + u, err := GetUserByName(name) + if err != nil { + continue + } + mails = append(mails, u.Email) + } + return mails +} + +// GetUserByEmail returns the user object by given e-mail if exists. +func GetUserByEmail(email string) (*User, error) { + if len(email) == 0 { + return nil, ErrUserNotExist + } + user := &User{Email: strings.ToLower(email)} + has, err := orm.Get(user) + if err != nil { return nil, err + } else if !has { + return nil, ErrUserNotExist } + return user, nil +} +// LoginUserPlain validates user by raw user name and password. +func LoginUserPlain(name, passwd string) (*User, error) { + user := User{LowerName: strings.ToLower(name)} has, err := orm.Get(&user) if err != nil { return nil, err } else if !has { - err = ErrUserNotExist + return nil, ErrUserNotExist + } + + newUser := &User{Passwd: passwd, Salt: user.Salt} + newUser.EncodePasswd() + if user.Passwd != newUser.Passwd { + return nil, ErrUserNotExist } - return &user, err + return &user, nil } // Follow is connection request for receiving user notifycation. |