* Update code.gitea.io/git * Update function calls * govendor fetchtags/v1.2.0-rc1
| @@ -907,7 +907,10 @@ func (pr *PullRequest) PushToBaseRepo() (err error) { | |||||
| _ = os.Remove(file) | _ = 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) | return fmt.Errorf("Push: %v", err) | ||||
| } | } | ||||
| @@ -2342,7 +2342,10 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st | |||||
| return fmt.Errorf("CreateNewBranch: %v", err) | 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) | return fmt.Errorf("Push: %v", err) | ||||
| } | } | ||||
| @@ -136,7 +136,10 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) ( | |||||
| Message: opts.Message, | Message: opts.Message, | ||||
| }); err != nil { | }); err != nil { | ||||
| return fmt.Errorf("CommitChanges: %v", err) | 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) | 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, | Message: opts.Message, | ||||
| }); err != nil { | }); err != nil { | ||||
| return fmt.Errorf("CommitChanges: %v", err) | 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) | 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, | Message: opts.Message, | ||||
| }); err != nil { | }); err != nil { | ||||
| return fmt.Errorf("CommitChanges: %v", err) | 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) | return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) | ||||
| } | } | ||||
| @@ -163,7 +163,10 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiPath, wikiPath, conten | |||||
| Message: message, | Message: message, | ||||
| }); err != nil { | }); err != nil { | ||||
| return fmt.Errorf("CommitChanges: %v", err) | 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) | return fmt.Errorf("Push: %v", err) | ||||
| } | } | ||||
| @@ -209,7 +212,10 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiPath string) (err error) | |||||
| Message: message, | Message: message, | ||||
| }); err != nil { | }); err != nil { | ||||
| return fmt.Errorf("CommitChanges: %v", err) | 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) | return fmt.Errorf("Push: %v", err) | ||||
| } | } | ||||
| @@ -12,3 +12,5 @@ Rémy Boulanouar <admin@dblk.org> (@DblK) | |||||
| Sandro Santilli <strk@kbt.io> (@strk) | Sandro Santilli <strk@kbt.io> (@strk) | ||||
| Thibault Meyer <meyer.thibault@gmail.com> (@0xbaadf00d) | Thibault Meyer <meyer.thibault@gmail.com> (@0xbaadf00d) | ||||
| Thomas Boerger <thomas@webhippie.de> (@tboerger) | Thomas Boerger <thomas@webhippie.de> (@tboerger) | ||||
| Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks) | |||||
| Antoine Girard <sapk@sapk.fr> (@sapk) | |||||
| @@ -1,6 +1,6 @@ | |||||
| IMPORT := code.gitea.io/git | 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 | GENERATE ?= code.gitea.io/git | ||||
| .PHONY: all | .PHONY: all | ||||
| @@ -18,7 +18,7 @@ generate: | |||||
| .PHONY: fmt | .PHONY: fmt | ||||
| 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 | .PHONY: vet | ||||
| vet: | vet: | ||||
| @@ -35,6 +35,10 @@ lint: | |||||
| test: | test: | ||||
| for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; | 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 | .PHONY: build | ||||
| build: | build: | ||||
| go build . | go build . | ||||
| @@ -70,7 +70,11 @@ func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, std | |||||
| return err | 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, | // RunInDirTimeout executes the command in given directory with given timeout, | ||||
| @@ -1,4 +1,5 @@ | |||||
| // Copyright 2015 The Gogs Authors. All rights reserved. | // 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 | // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||
| @@ -152,9 +153,21 @@ func Pull(repoPath string, opts PullRemoteOptions) error { | |||||
| return err | 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. | // 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 | return err | ||||
| } | } | ||||
| @@ -261,3 +274,14 @@ func parseSize(objects string) *CountObject { | |||||
| } | } | ||||
| return repoSize | 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) | |||||
| } | |||||
| @@ -66,19 +66,15 @@ func (repo *Repository) SetDefaultBranch(name string) error { | |||||
| // GetBranches returns all branches of the repository. | // GetBranches returns all branches of the repository. | ||||
| func (repo *Repository) GetBranches() ([]string, error) { | 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 { | if err != nil { | ||||
| return nil, err | 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 | return branches, nil | ||||
| } | } | ||||
| @@ -5,10 +5,7 @@ | |||||
| package git | package git | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "path" | |||||
| "path/filepath" | "path/filepath" | ||||
| "runtime" | |||||
| "sort" | "sort" | ||||
| "strconv" | "strconv" | ||||
| "strings" | "strings" | ||||
| @@ -147,112 +144,147 @@ func (tes Entries) Sort() { | |||||
| sort.Sort(tes) | 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) { | 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 | 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 | |||||
| } | } | ||||
| @@ -3,10 +3,10 @@ | |||||
| "ignore": "test appengine", | "ignore": "test appengine", | ||||
| "package": [ | "package": [ | ||||
| { | { | ||||
| "checksumSHA1": "wvujc6YaEoP3g2K+USjIcZcrRgQ=", | |||||
| "checksumSHA1": "iqK8QebzwEcNL2iIRpMAOYwb7Zg=", | |||||
| "path": "code.gitea.io/git", | "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=", | "checksumSHA1": "nLhT+bLMj8uLICP+EZbrdoQe6mM=", | ||||