diff --git a/models/pull.go b/models/pull.go index 76a646200..e85bf56c6 100644 --- a/models/pull.go +++ b/models/pull.go @@ -907,7 +907,10 @@ func (pr *PullRequest) PushToBaseRepo() (err error) { _ = os.Remove(file) - if err = git.Push(headRepoPath, tmpRemoteName, fmt.Sprintf("%s:%s", pr.HeadBranch, headFile)); err != nil { + if err = git.Push(headRepoPath, git.PushOptions{ + Remote: tmpRemoteName, + Branch: fmt.Sprintf("%s:%s", pr.HeadBranch, headFile), + }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/models/repo.go b/models/repo.go index ecfc296c9..9f5318e2f 100644 --- a/models/repo.go +++ b/models/repo.go @@ -2342,7 +2342,10 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st return fmt.Errorf("CreateNewBranch: %v", err) } - if err = git.Push(localPath, "origin", branchName); err != nil { + if err = git.Push(localPath, git.PushOptions{ + Remote: "origin", + Branch: branchName, + }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/models/repo_editor.go b/models/repo_editor.go index c675e1cfa..40898dd4c 100644 --- a/models/repo_editor.go +++ b/models/repo_editor.go @@ -136,7 +136,10 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) ( Message: opts.Message, }); err != nil { return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", opts.NewBranch); err != nil { + } else if err = git.Push(localPath, git.PushOptions{ + Remote: "origin", + Branch: opts.NewBranch, + }); err != nil { return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) } @@ -273,7 +276,10 @@ func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) ( Message: opts.Message, }); err != nil { return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", opts.NewBranch); err != nil { + } else if err = git.Push(localPath, git.PushOptions{ + Remote: "origin", + Branch: opts.NewBranch, + }); err != nil { return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) } @@ -509,7 +515,10 @@ func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) Message: opts.Message, }); err != nil { return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", opts.NewBranch); err != nil { + } else if err = git.Push(localPath, git.PushOptions{ + Remote: "origin", + Branch: opts.NewBranch, + }); err != nil { return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) } diff --git a/models/wiki.go b/models/wiki.go index d864505d5..a4a9cb9dc 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -163,7 +163,10 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiPath, wikiPath, conten Message: message, }); err != nil { return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", "master"); err != nil { + } else if err = git.Push(localPath, git.PushOptions{ + Remote: "origin", + Branch: "master", + }); err != nil { return fmt.Errorf("Push: %v", err) } @@ -209,7 +212,10 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiPath string) (err error) Message: message, }); err != nil { return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", "master"); err != nil { + } else if err = git.Push(localPath, git.PushOptions{ + Remote: "origin", + Branch: "master", + }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/vendor/code.gitea.io/git/MAINTAINERS b/vendor/code.gitea.io/git/MAINTAINERS index 495a41879..bcd086da7 100644 --- a/vendor/code.gitea.io/git/MAINTAINERS +++ b/vendor/code.gitea.io/git/MAINTAINERS @@ -12,3 +12,5 @@ Rémy Boulanouar (@DblK) Sandro Santilli (@strk) Thibault Meyer (@0xbaadf00d) Thomas Boerger (@tboerger) +Lauris Bukšis-Haberkorns (@lafriks) +Antoine Girard (@sapk) diff --git a/vendor/code.gitea.io/git/Makefile b/vendor/code.gitea.io/git/Makefile index 683b8e522..e659a5968 100644 --- a/vendor/code.gitea.io/git/Makefile +++ b/vendor/code.gitea.io/git/Makefile @@ -1,6 +1,6 @@ IMPORT := code.gitea.io/git -PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +PACKAGES ?= $(shell go list -e ./... | grep -v /vendor/ | grep -v /benchmark/) GENERATE ?= code.gitea.io/git .PHONY: all @@ -18,7 +18,7 @@ generate: .PHONY: fmt fmt: - find . -name "*.go" -type f -not -path "./vendor/*" | xargs gofmt -s -w + find . -name "*.go" -type f -not -path "./vendor/*" -not -path "./benchmark/*" | xargs gofmt -s -w .PHONY: vet vet: @@ -35,6 +35,10 @@ lint: test: for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; +.PHONY: bench +bench: + go test -run=XXXXXX -benchtime=10s -bench=. || exit 1 + .PHONY: build build: go build . diff --git a/vendor/code.gitea.io/git/command.go b/vendor/code.gitea.io/git/command.go index c3534a145..06722c213 100644 --- a/vendor/code.gitea.io/git/command.go +++ b/vendor/code.gitea.io/git/command.go @@ -70,7 +70,11 @@ func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, std return err } - return cmd.Wait() + if err := cmd.Wait(); err != nil { + return err + } + + return ctx.Err() } // RunInDirTimeout executes the command in given directory with given timeout, diff --git a/vendor/code.gitea.io/git/repo.go b/vendor/code.gitea.io/git/repo.go index 15321b94c..f87c73d35 100644 --- a/vendor/code.gitea.io/git/repo.go +++ b/vendor/code.gitea.io/git/repo.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -152,9 +153,21 @@ func Pull(repoPath string, opts PullRemoteOptions) error { return err } +// PushOptions options when push to remote +type PushOptions struct { + Remote string + Branch string + Force bool +} + // Push pushs local commits to given remote branch. -func Push(repoPath, remote, branch string) error { - _, err := NewCommand("push", remote, branch).RunInDir(repoPath) +func Push(repoPath string, opts PushOptions) error { + cmd := NewCommand("push") + if opts.Force { + cmd.AddArguments("-f") + } + cmd.AddArguments(opts.Remote, opts.Branch) + _, err := cmd.RunInDir(repoPath) return err } @@ -261,3 +274,14 @@ func parseSize(objects string) *CountObject { } return repoSize } + +// GetLatestCommitTime returns time for latest commit in repository (across all branches) +func GetLatestCommitTime(repoPath string) (time.Time, error) { + cmd := NewCommand("for-each-ref", "--sort=-committerdate", "refs/heads/", "--count", "1", "--format=%(committerdate)") + stdout, err := cmd.RunInDir(repoPath) + if err != nil { + return time.Time{}, err + } + commitTime := strings.TrimSpace(stdout) + return time.Parse("Mon Jan 02 15:04:05 2006 -0700", commitTime) +} diff --git a/vendor/code.gitea.io/git/repo_branch.go b/vendor/code.gitea.io/git/repo_branch.go index 8366b6833..bf9d19811 100644 --- a/vendor/code.gitea.io/git/repo_branch.go +++ b/vendor/code.gitea.io/git/repo_branch.go @@ -66,19 +66,15 @@ func (repo *Repository) SetDefaultBranch(name string) error { // GetBranches returns all branches of the repository. func (repo *Repository) GetBranches() ([]string, error) { - stdout, err := NewCommand("show-ref", "--heads").RunInDir(repo.Path) + stdout, err := NewCommand("for-each-ref", "--format=%(refname)", BranchPrefix).RunInDir(repo.Path) if err != nil { return nil, err } - infos := strings.Split(stdout, "\n") - branches := make([]string, len(infos)-1) - for i, info := range infos[:len(infos)-1] { - fields := strings.Fields(info) - if len(fields) != 2 { - continue // NOTE: I should believe git will not give me wrong string. - } - branches[i] = strings.TrimPrefix(fields[1], BranchPrefix) + refs := strings.Split(stdout, "\n") + branches := make([]string, len(refs)-1) + for i, ref := range refs[:len(refs)-1] { + branches[i] = strings.TrimPrefix(ref, BranchPrefix) } return branches, nil } diff --git a/vendor/code.gitea.io/git/tree_entry.go b/vendor/code.gitea.io/git/tree_entry.go index 1e4934e81..58c513246 100644 --- a/vendor/code.gitea.io/git/tree_entry.go +++ b/vendor/code.gitea.io/git/tree_entry.go @@ -5,10 +5,7 @@ package git import ( - "fmt" - "path" "path/filepath" - "runtime" "sort" "strconv" "strings" @@ -147,112 +144,147 @@ func (tes Entries) Sort() { sort.Sort(tes) } -type commitInfo struct { - entryName string - infos []interface{} - err error +// getCommitInfoState transient state for getting commit info for entries +type getCommitInfoState struct { + entries map[string]*TreeEntry // map from filepath to entry + commits map[string]*Commit // map from entry name to commit + lastCommitHash string + lastCommit *Commit + treePath string + headCommit *Commit + nextSearchSize int // next number of commits to search for } -// GetCommitsInfo takes advantages of concurrency to speed up getting information -// of all commits that are corresponding to these entries. This method will automatically -// choose the right number of goroutine (concurrency) to use related of the host CPU. +func initGetCommitInfoState(entries Entries, headCommit *Commit, treePath string) *getCommitInfoState { + entriesByPath := make(map[string]*TreeEntry, len(entries)) + for _, entry := range entries { + entriesByPath[filepath.Join(treePath, entry.Name())] = entry + } + return &getCommitInfoState{ + entries: entriesByPath, + commits: make(map[string]*Commit, len(entriesByPath)), + treePath: treePath, + headCommit: headCommit, + nextSearchSize: 16, + } +} + +// GetCommitsInfo gets information of all commits that are corresponding to these entries func (tes Entries) GetCommitsInfo(commit *Commit, treePath string) ([][]interface{}, error) { - return tes.GetCommitsInfoWithCustomConcurrency(commit, treePath, 0) + state := initGetCommitInfoState(tes, commit, treePath) + if err := getCommitsInfo(state); err != nil { + return nil, err + } + + commitsInfo := make([][]interface{}, len(tes)) + for i, entry := range tes { + commit = state.commits[filepath.Join(treePath, entry.Name())] + switch entry.Type { + case ObjectCommit: + subModuleURL := "" + if subModule, err := state.headCommit.GetSubModule(entry.Name()); err != nil { + return nil, err + } else if subModule != nil { + subModuleURL = subModule.URL + } + subModuleFile := NewSubModuleFile(commit, subModuleURL, entry.ID.String()) + commitsInfo[i] = []interface{}{entry, subModuleFile} + default: + commitsInfo[i] = []interface{}{entry, commit} + } + } + return commitsInfo, nil } -// GetCommitsInfoWithCustomConcurrency takes advantages of concurrency to speed up getting information -// of all commits that are corresponding to these entries. If the given maxConcurrency is negative or -// equal to zero: the right number of goroutine (concurrency) to use will be chosen related of the -// host CPU. -func (tes Entries) GetCommitsInfoWithCustomConcurrency(commit *Commit, treePath string, maxConcurrency int) ([][]interface{}, error) { - if len(tes) == 0 { - return nil, nil +func (state *getCommitInfoState) nextCommit(hash string) { + state.lastCommitHash = hash + state.lastCommit = nil +} + +func (state *getCommitInfoState) commit() (*Commit, error) { + var err error + if state.lastCommit == nil { + state.lastCommit, err = state.headCommit.repo.GetCommit(state.lastCommitHash) } + return state.lastCommit, err +} - if maxConcurrency <= 0 { - maxConcurrency = runtime.NumCPU() +func (state *getCommitInfoState) update(path string) error { + relPath, err := filepath.Rel(state.treePath, path) + if err != nil { + return nil + } + var entryPath string + if index := strings.IndexRune(relPath, '/'); index >= 0 { + entryPath = filepath.Join(state.treePath, relPath[:index]) + } else { + entryPath = path } + if _, ok := state.entries[entryPath]; !ok { + return nil + } else if _, ok := state.commits[entryPath]; ok { + return nil + } + state.commits[entryPath], err = state.commit() + return err +} - // Length of taskChan determines how many goroutines (subprocesses) can run at the same time. - // The length of revChan should be same as taskChan so goroutines whoever finished job can - // exit as early as possible, only store data inside channel. - taskChan := make(chan bool, maxConcurrency) - revChan := make(chan commitInfo, maxConcurrency) - doneChan := make(chan error) - - // Receive loop will exit when it collects same number of data pieces as tree entries. - // It notifies doneChan before exits or notify early with possible error. - infoMap := make(map[string][]interface{}, len(tes)) - go func() { - i := 0 - for info := range revChan { - if info.err != nil { - doneChan <- info.err - return - } +func getCommitsInfo(state *getCommitInfoState) error { + for len(state.entries) > len(state.commits) { + if err := getNextCommitInfos(state); err != nil { + return err + } + } + return nil +} - infoMap[info.entryName] = info.infos - i++ - if i == len(tes) { +func getNextCommitInfos(state *getCommitInfoState) error { + logOutput, err := logCommand(state.lastCommitHash, state).RunInDir(state.headCommit.repo.Path) + if err != nil { + return err + } + lines := strings.Split(logOutput, "\n") + i := 0 + for i < len(lines) { + state.nextCommit(lines[i]) + i++ + for ; i < len(lines); i++ { + path := lines[i] + if path == "" { break } + state.update(path) } - doneChan <- nil - }() - - for i := range tes { - // When taskChan is idle (or has empty slots), put operation will not block. - // However when taskChan is full, code will block and wait any running goroutines to finish. - taskChan <- true - - if tes[i].Type != ObjectCommit { - go func(i int) { - cinfo := commitInfo{entryName: tes[i].Name()} - c, err := commit.GetCommitByPath(filepath.Join(treePath, tes[i].Name())) - if err != nil { - cinfo.err = fmt.Errorf("GetCommitByPath (%s/%s): %v", treePath, tes[i].Name(), err) - } else { - cinfo.infos = []interface{}{tes[i], c} - } - revChan <- cinfo - <-taskChan // Clear one slot from taskChan to allow new goroutines to start. - }(i) - continue + i++ // skip blank line + if len(state.entries) == len(state.commits) { + break } - - // Handle submodule - go func(i int) { - cinfo := commitInfo{entryName: tes[i].Name()} - sm, err := commit.GetSubModule(path.Join(treePath, tes[i].Name())) - if err != nil && !IsErrNotExist(err) { - cinfo.err = fmt.Errorf("GetSubModule (%s/%s): %v", treePath, tes[i].Name(), err) - revChan <- cinfo - return - } - - smURL := "" - if sm != nil { - smURL = sm.URL - } - - c, err := commit.GetCommitByPath(filepath.Join(treePath, tes[i].Name())) - if err != nil { - cinfo.err = fmt.Errorf("GetCommitByPath (%s/%s): %v", treePath, tes[i].Name(), err) - } else { - cinfo.infos = []interface{}{tes[i], NewSubModuleFile(c, smURL, tes[i].ID.String())} - } - revChan <- cinfo - <-taskChan - }(i) } + return nil +} - if err := <-doneChan; err != nil { - return nil, err +func logCommand(exclusiveStartHash string, state *getCommitInfoState) *Command { + var commitHash string + if len(exclusiveStartHash) == 0 { + commitHash = "HEAD" + } else { + commitHash = exclusiveStartHash + "^" } - - commitsInfo := make([][]interface{}, len(tes)) - for i := 0; i < len(tes); i++ { - commitsInfo[i] = infoMap[tes[i].Name()] + var command *Command + numRemainingEntries := len(state.entries) - len(state.commits) + if numRemainingEntries < 32 { + searchSize := (numRemainingEntries + 1) / 2 + command = NewCommand("log", prettyLogFormat, "--name-only", + "-"+strconv.Itoa(searchSize), commitHash, "--") + for path, entry := range state.entries { + if _, ok := state.commits[entry.Name()]; !ok { + command.AddArguments(path) + } + } + } else { + command = NewCommand("log", prettyLogFormat, "--name-only", + "-"+strconv.Itoa(state.nextSearchSize), commitHash, "--", state.treePath) } - return commitsInfo, nil + state.nextSearchSize += state.nextSearchSize + return command } diff --git a/vendor/vendor.json b/vendor/vendor.json index 109f67b99..2a910aef1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,10 +3,10 @@ "ignore": "test appengine", "package": [ { - "checksumSHA1": "wvujc6YaEoP3g2K+USjIcZcrRgQ=", + "checksumSHA1": "iqK8QebzwEcNL2iIRpMAOYwb7Zg=", "path": "code.gitea.io/git", - "revision": "ed175468f8debc8cdb2fa495fc157f1b9d046628", - "revisionTime": "2017-05-04T06:58:26Z" + "revision": "c98a6f353a13546ae13e7391020cadd750181585", + "revisionTime": "2017-05-29T11:48:12Z" }, { "checksumSHA1": "nLhT+bLMj8uLICP+EZbrdoQe6mM=",