From 034c4b9e891499ba88b10ba2b8ef2b71e8e47b46 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 01:08:19 +0200 Subject: [PATCH 01/35] Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 362 ++++++++----------------------------- modules/git/parse.go | 25 +-- modules/git/repo.go | 23 ++- modules/git/sha1.go | 4 +- modules/git/signature.go | 8 +- modules/git/tree.go | 75 +++++--- modules/git/tree_blob.go | 17 +- modules/git/tree_entry.go | 43 +++-- 8 files changed, 200 insertions(+), 357 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 971082be1fb73..6e1f0e0f0b11f 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -5,325 +5,115 @@ package git import ( - "bufio" - "context" - "fmt" - "os/exec" "path" - "runtime" - "strconv" - "strings" - "sync" - "time" -) -const ( - // parameters for searching for commit infos. If the untargeted search has - // not found any entries in the past 5 commits, and 12 or fewer entries - // remain, then we'll just let the targeted-searching threads finish off, - // and stop the untargeted search to not interfere. - deferToTargetedSearchColdStreak = 5 - deferToTargetedSearchNumRemainingEntries = 12 + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" ) -// getCommitsInfoState shared state while getting commit info for entries -type getCommitsInfoState struct { - lock sync.Mutex - /* read-only fields, can be read without the mutex */ - // entries and entryPaths are read-only after initialization, so they can - // safely be read without the mutex - entries []*TreeEntry - // set of filepaths to get info for - entryPaths map[string]struct{} - treePath string - headCommit *Commit - - /* mutable fields, must hold mutex to read or write */ - // map from filepath to commit - commits map[string]*Commit - // set of filepaths that have been or are being searched for in a target search - targetedPaths map[string]struct{} -} - -func (state *getCommitsInfoState) numRemainingEntries() int { - state.lock.Lock() - defer state.lock.Unlock() - return len(state.entries) - len(state.commits) -} - -// getTargetEntryPath Returns the next path for a targeted-searching thread to -// search for, or returns the empty string if nothing left to search for -func (state *getCommitsInfoState) getTargetedEntryPath() string { - var targetedEntryPath string - state.lock.Lock() - defer state.lock.Unlock() - for _, entry := range state.entries { - entryPath := path.Join(state.treePath, entry.Name()) - if _, ok := state.commits[entryPath]; ok { - continue - } else if _, ok = state.targetedPaths[entryPath]; ok { - continue - } - targetedEntryPath = entryPath - state.targetedPaths[entryPath] = struct{}{} - break - } - return targetedEntryPath -} - -// repeatedly perform targeted searches for unpopulated entries -func targetedSearch(state *getCommitsInfoState, done chan error, cache LastCommitCache) { - for { - entryPath := state.getTargetedEntryPath() - if len(entryPath) == 0 { - done <- nil - return - } - if cache != nil { - commit, err := cache.Get(state.headCommit.repo.Path, state.headCommit.ID.String(), entryPath) - if err == nil && commit != nil { - state.update(entryPath, commit) - continue - } - } - command := NewCommand("rev-list", "-1", state.headCommit.ID.String(), "--", entryPath) - output, err := command.RunInDir(state.headCommit.repo.Path) - if err != nil { - done <- err - return - } - id, err := NewIDFromString(strings.TrimSpace(output)) - if err != nil { - done <- err - return - } - commit, err := state.headCommit.repo.getCommit(id) - if err != nil { - done <- err - return - } - state.update(entryPath, commit) - if cache != nil { - cache.Put(state.headCommit.repo.Path, state.headCommit.ID.String(), entryPath, commit) - } +// GetCommitsInfo gets information of all commits that are corresponding to these entries +func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, error) { + entryPaths := make([]string, len(tes)) + for i, entry := range tes { + entryPaths[i] = path.Join(treePath, entry.Name()) } -} -func initGetCommitInfoState(entries Entries, headCommit *Commit, treePath string) *getCommitsInfoState { - entryPaths := make(map[string]struct{}, len(entries)) - for _, entry := range entries { - entryPaths[path.Join(treePath, entry.Name())] = struct{}{} - } - if treePath = path.Clean(treePath); treePath == "." { - treePath = "" - } - return &getCommitsInfoState{ - entries: entries, - entryPaths: entryPaths, - commits: make(map[string]*Commit, len(entries)), - targetedPaths: make(map[string]struct{}, len(entries)), - treePath: treePath, - headCommit: headCommit, + c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID)) + if err != nil { + return nil, err } -} -// GetCommitsInfo gets information of all commits that are corresponding to these entries -func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, error) { - state := initGetCommitInfoState(tes, commit, treePath) - if err := getCommitsInfo(state, cache); err != nil { + revs, err := getLastCommitForPaths(c, entryPaths) + if err != nil { return nil, err } - if len(state.commits) < len(state.entryPaths) { - return nil, fmt.Errorf("could not find commits for all entries") - } + + commit.repo.gogitStorage.Close() commitsInfo := make([][]interface{}, len(tes)) for i, entry := range tes { - commit, ok := state.commits[path.Join(treePath, entry.Name())] - if !ok { - return nil, fmt.Errorf("could not find commit for %s", 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} + commit := &Commit{ + ID: SHA1(revs[i].Hash), + CommitMessage: revs[i].Message, + Committer: &Signature{ + When: revs[i].Committer.When, + }, } + commitsInfo[i] = []interface{}{entry, commit} } return commitsInfo, nil } -func (state *getCommitsInfoState) cleanEntryPath(rawEntryPath string) (string, error) { - if rawEntryPath[0] == '"' { - var err error - rawEntryPath, err = strconv.Unquote(rawEntryPath) - if err != nil { - return rawEntryPath, err - } - } - var entryNameStartIndex int - if len(state.treePath) > 0 { - entryNameStartIndex = len(state.treePath) + 1 - } - - if index := strings.IndexByte(rawEntryPath[entryNameStartIndex:], '/'); index >= 0 { - return rawEntryPath[:entryNameStartIndex+index], nil - } - return rawEntryPath, nil -} +func getLastCommitForPaths(c *object.Commit, paths []string) ([]*object.Commit, error) { + cIter := object.NewCommitIterCTime(c, nil, nil) + result := make([]*object.Commit, len(paths)) + remainingResults := len(paths) -// update report that the given path was last modified by the given commit. -// Returns whether state.commits was updated -func (state *getCommitsInfoState) update(entryPath string, commit *Commit) bool { - if _, ok := state.entryPaths[entryPath]; !ok { - return false - } - - var updated bool - state.lock.Lock() - defer state.lock.Unlock() - if _, ok := state.commits[entryPath]; !ok { - state.commits[entryPath] = commit - updated = true - } - return updated -} - -const getCommitsInfoPretty = "--pretty=format:%H %ct %s" - -func getCommitsInfo(state *getCommitsInfoState, cache LastCommitCache) error { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancel() - - args := []string{"log", state.headCommit.ID.String(), getCommitsInfoPretty, "--name-status", "-c"} - if len(state.treePath) > 0 { - args = append(args, "--", state.treePath) - } - cmd := exec.CommandContext(ctx, "git", args...) - cmd.Dir = state.headCommit.repo.Path - - readCloser, err := cmd.StdoutPipe() + cTree, err := c.Tree() if err != nil { - return err - } - - if err := cmd.Start(); err != nil { - return err - } - // it's okay to ignore the error returned by cmd.Wait(); we expect the - // subprocess to sometimes have a non-zero exit status, since we may - // prematurely close stdout, resulting in a broken pipe. - defer cmd.Wait() - - numThreads := runtime.NumCPU() - done := make(chan error, numThreads) - for i := 0; i < numThreads; i++ { - go targetedSearch(state, done, cache) - } - - scanner := bufio.NewScanner(readCloser) - err = state.processGitLogOutput(scanner) - - // it is important that we close stdout here; if we do not close - // stdout, the subprocess will keep running, and the deffered call - // cmd.Wait() may block for a long time. - if closeErr := readCloser.Close(); closeErr != nil && err == nil { - err = closeErr + return nil, err } - for i := 0; i < numThreads; i++ { - doneErr := <-done - if doneErr != nil && err == nil { - err = doneErr + currentEntryHashes := make([]plumbing.Hash, len(paths)) + for i, path := range paths { + cEntry, err := cTree.FindEntry(path) + if err != nil { + return nil, err } + currentEntryHashes[i] = cEntry.Hash } - return err -} -func (state *getCommitsInfoState) processGitLogOutput(scanner *bufio.Scanner) error { - // keep a local cache of seen paths to avoid acquiring a lock for paths - // we've already seen - seenPaths := make(map[string]struct{}, len(state.entryPaths)) - // number of consecutive commits without any finds - coldStreak := 0 - var commit *Commit - var err error - for scanner.Scan() { - line := scanner.Text() - if len(line) == 0 { // in-between commits - numRemainingEntries := state.numRemainingEntries() - if numRemainingEntries == 0 { - break - } - if coldStreak >= deferToTargetedSearchColdStreak && - numRemainingEntries <= deferToTargetedSearchNumRemainingEntries { - // stop this untargeted search, and let the targeted-search threads - // finish the work - break - } - continue - } - if line[0] >= 'A' && line[0] <= 'X' { // a file was changed by the current commit - // look for the last tab, since for copies (C) and renames (R) two - // filenames are printed: src, then dest - tabIndex := strings.LastIndexByte(line, '\t') - if tabIndex < 1 { - return fmt.Errorf("misformatted line: %s", line) - } - entryPath, err := state.cleanEntryPath(line[tabIndex+1:]) + cIter.ForEach(func(current *object.Commit) error { + newEntryHashes := make([]plumbing.Hash, len(paths)) + + err := current.Parents().ForEach(func(parent *object.Commit) error { + parentTree, err := parent.Tree() if err != nil { return err } - if _, ok := seenPaths[entryPath]; !ok { - if state.update(entryPath, commit) { - coldStreak = 0 + + for i, path := range paths { + // skip path if we already found it + if currentEntryHashes[i] != plumbing.ZeroHash { + // find parents that contain the path + if parentEntry, err := parentTree.FindEntry(path); err == nil { + // if the hash for the path differs in the parent then the current commit changed it + if parentEntry.Hash == currentEntryHashes[i] { + newEntryHashes[i] = currentEntryHashes[i] + } else { + // mark for saving the result below + newEntryHashes[i] = plumbing.ZeroHash + // stop any further processing for this file + currentEntryHashes[i] = plumbing.ZeroHash + } + } } - seenPaths[entryPath] = struct{}{} } - continue - } - // a new commit - commit, err = parseCommitInfo(line) + return nil + }) if err != nil { return err } - coldStreak++ - } - return scanner.Err() -} -// parseCommitInfo parse a commit from a line of `git log` output. Expects the -// line to be formatted according to getCommitsInfoPretty. -func parseCommitInfo(line string) (*Commit, error) { - if len(line) < 43 { - return nil, fmt.Errorf("invalid git output: %s", line) - } - ref, err := NewIDFromString(line[:40]) - if err != nil { - return nil, err - } - spaceIndex := strings.IndexByte(line[41:], ' ') - if spaceIndex < 0 { - return nil, fmt.Errorf("invalid git output: %s", line) - } - unixSeconds, err := strconv.Atoi(line[41 : 41+spaceIndex]) - if err != nil { - return nil, err - } - message := line[spaceIndex+42:] - return &Commit{ - ID: ref, - CommitMessage: message, - Committer: &Signature{ - When: time.Unix(int64(unixSeconds), 0), - }, - }, nil + // if a file didn't exist in any parent commit then it must have been created in the + // current one. also we mark changed files in the loop above as not present in the + // parent to simplify processing + for i, newEntryHash := range newEntryHashes { + if newEntryHash == plumbing.ZeroHash && result[i] == nil { + result[i] = current + remainingResults-- + } + } + + if remainingResults == 0 { + return storer.ErrStop + } + + currentEntryHashes = newEntryHashes + return nil + }) + + return result, nil } diff --git a/modules/git/parse.go b/modules/git/parse.go index 5c964f16ee5f7..22861b1d2cb2f 100644 --- a/modules/git/parse.go +++ b/modules/git/parse.go @@ -8,6 +8,10 @@ import ( "bytes" "fmt" "strconv" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/filemode" + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // ParseTreeEntries parses the output of a `git ls-tree` command. @@ -20,30 +24,26 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { for pos := 0; pos < len(data); { // expect line to be of the form " \t" entry := new(TreeEntry) + entry.gogitTreeEntry = &object.TreeEntry{} entry.ptree = ptree if pos+6 > len(data) { return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) } switch string(data[pos : pos+6]) { case "100644": - entry.mode = EntryModeBlob - entry.Type = ObjectBlob + entry.gogitTreeEntry.Mode = filemode.Regular pos += 12 // skip over "100644 blob " case "100755": - entry.mode = EntryModeExec - entry.Type = ObjectBlob + entry.gogitTreeEntry.Mode = filemode.Executable pos += 12 // skip over "100755 blob " case "120000": - entry.mode = EntryModeSymlink - entry.Type = ObjectBlob + entry.gogitTreeEntry.Mode = filemode.Symlink pos += 12 // skip over "120000 blob " case "160000": - entry.mode = EntryModeCommit - entry.Type = ObjectCommit + entry.gogitTreeEntry.Mode = filemode.Submodule pos += 14 // skip over "160000 object " case "040000": - entry.mode = EntryModeTree - entry.Type = ObjectTree + entry.gogitTreeEntry.Mode = filemode.Dir pos += 12 // skip over "040000 tree " default: return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6])) @@ -57,6 +57,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { return nil, fmt.Errorf("Invalid ls-tree output: %v", err) } entry.ID = id + entry.gogitTreeEntry.Hash = plumbing.Hash(id) pos += 41 // skip over sha and trailing space end := pos + bytes.IndexByte(data[pos:], '\n') @@ -66,12 +67,12 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { // In case entry name is surrounded by double quotes(it happens only in git-shell). if data[pos] == '"' { - entry.name, err = strconv.Unquote(string(data[pos:end])) + entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end])) if err != nil { return nil, fmt.Errorf("Invalid ls-tree output: %v", err) } } else { - entry.name = string(data[pos:end]) + entry.gogitTreeEntry.Name = string(data[pos:end]) } pos = end + 1 diff --git a/modules/git/repo.go b/modules/git/repo.go index 4306730920eb2..252a84f3d18f4 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -16,6 +16,11 @@ import ( "time" "github.com/Unknwon/com" + + "gopkg.in/src-d/go-billy.v4/osfs" + gogit "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/cache" + "gopkg.in/src-d/go-git.v4/storage/filesystem" ) // Repository represents a Git repository. @@ -24,6 +29,9 @@ type Repository struct { commitCache *ObjectCache tagCache *ObjectCache + + gogitRepo *gogit.Repository + gogitStorage *filesystem.Storage } const prettyLogFormat = `--pretty=format:%H` @@ -77,10 +85,19 @@ func OpenRepository(repoPath string) (*Repository, error) { return nil, errors.New("no such file or directory") } + fs := osfs.New(repoPath) + storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true}) + gogitRepo, err := gogit.Open(storage, fs) + if err != nil { + return nil, err + } + return &Repository{ - Path: repoPath, - commitCache: newObjectCache(), - tagCache: newObjectCache(), + Path: repoPath, + gogitRepo: gogitRepo, + gogitStorage: storage, + commitCache: newObjectCache(), + tagCache: newObjectCache(), }, nil } diff --git a/modules/git/sha1.go b/modules/git/sha1.go index 6c9d53949dff9..582cb3c2dfd78 100644 --- a/modules/git/sha1.go +++ b/modules/git/sha1.go @@ -9,13 +9,15 @@ import ( "encoding/hex" "fmt" "strings" + + "gopkg.in/src-d/go-git.v4/plumbing" ) // EmptySHA defines empty git SHA const EmptySHA = "0000000000000000000000000000000000000000" // SHA1 a git commit name -type SHA1 [20]byte +type SHA1 plumbing.Hash // Equal returns true if s has the same SHA1 as caller. // Support 40-length-string, []byte, SHA1. diff --git a/modules/git/signature.go b/modules/git/signature.go index e6ab247fd73fa..e73f908e9a536 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -8,14 +8,12 @@ import ( "bytes" "strconv" "time" + + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // Signature represents the Author or Committer information. -type Signature struct { - Email string - Name string - When time.Time -} +type Signature object.Signature const ( // GitTimeLayout is the (default) time layout used by git. diff --git a/modules/git/tree.go b/modules/git/tree.go index 5ec22a3a6f409..98deced83ab09 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -5,7 +5,11 @@ package git import ( + "io" "strings" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // Tree represents a flat directory listing. @@ -13,14 +17,10 @@ type Tree struct { ID SHA1 repo *Repository + gogitTree *object.Tree + // parent tree ptree *Tree - - entries Entries - entriesParsed bool - - entriesRecursive Entries - entriesRecursiveParsed bool } // NewTree create a new tree according the repository and commit id @@ -62,37 +62,60 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) { // ListEntries returns all entries of current tree. func (t *Tree) ListEntries() (Entries, error) { - if t.entriesParsed { - return t.entries, nil - } + if t.gogitTree == nil { + gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID)) + if err != nil { + return nil, err + } - stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path) - if err != nil { - return nil, err + t.gogitTree = gogitTree } - t.entries, err = parseTreeEntries(stdout, t) - if err == nil { - t.entriesParsed = true + entries := make([]*TreeEntry, len(t.gogitTree.Entries)) + for i, entry := range t.gogitTree.Entries { + entries[i] = &TreeEntry{ + ID: SHA1(entry.Hash), + gogitTreeEntry: &t.gogitTree.Entries[i], + ptree: t, + } } - return t.entries, err + return entries, nil } // ListEntriesRecursive returns all entries of current tree recursively including all subtrees func (t *Tree) ListEntriesRecursive() (Entries, error) { - if t.entriesRecursiveParsed { - return t.entriesRecursive, nil - } - stdout, err := NewCommand("ls-tree", "-t", "-r", t.ID.String()).RunInDirBytes(t.repo.Path) - if err != nil { - return nil, err + if t.gogitTree == nil { + gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID)) + if err != nil { + return nil, err + } + + t.gogitTree = gogitTree } - t.entriesRecursive, err = parseTreeEntries(stdout, t) - if err == nil { - t.entriesRecursiveParsed = true + var entries []*TreeEntry + seen := map[plumbing.Hash]bool{} + walker := object.NewTreeWalker(t.gogitTree, true, seen) + for { + _, entry, err := walker.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if seen[entry.Hash] { + continue + } + + convertedEntry := &TreeEntry{ + ID: SHA1(entry.Hash), + gogitTreeEntry: &entry, + ptree: t, + } + entries = append(entries, convertedEntry) } - return t.entriesRecursive, err + return entries, nil } diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index a37f6b22791cd..4a11e4aea096e 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -7,15 +7,24 @@ package git import ( "path" "strings" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/filemode" + + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // GetTreeEntryByPath get the tree entries according the sub dir func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { if len(relpath) == 0 { return &TreeEntry{ - ID: t.ID, - Type: ObjectTree, - mode: EntryModeTree, + ID: t.ID, + //Type: ObjectTree, + gogitTreeEntry: &object.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: plumbing.Hash(t.ID), + }, }, nil } @@ -30,7 +39,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { return nil, err } for _, v := range entries { - if v.name == name { + if v.Name() == name { return v, nil } } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 5b74e9a695244..910ffb5de0b1d 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -7,8 +7,11 @@ package git import ( "io" "sort" - "strconv" "strings" + + "gopkg.in/src-d/go-git.v4/plumbing/filemode" + + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // EntryMode the type of the object in the git tree @@ -31,15 +34,10 @@ const ( // TreeEntry the leaf in the git tree type TreeEntry struct { - ID SHA1 - Type ObjectType - - mode EntryMode - name string + ID SHA1 - ptree *Tree - - committed bool + gogitTreeEntry *object.TreeEntry + ptree *Tree size int64 sized bool @@ -47,7 +45,7 @@ type TreeEntry struct { // Name returns the name of the entry func (te *TreeEntry) Name() string { - return te.name + return te.gogitTreeEntry.Name } // Mode returns the mode of the entry @@ -63,29 +61,34 @@ func (te *TreeEntry) Size() int64 { return te.size } - stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path) + file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry) if err != nil { return 0 } te.sized = true - te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) + te.size = file.Size return te.size } // IsSubModule if the entry is a sub module func (te *TreeEntry) IsSubModule() bool { - return te.mode == EntryModeCommit + return te.gogitTreeEntry.Mode == filemode.Submodule } // IsDir if the entry is a sub dir func (te *TreeEntry) IsDir() bool { - return te.mode == EntryModeTree + return te.gogitTreeEntry.Mode == filemode.Dir } // IsLink if the entry is a symlink func (te *TreeEntry) IsLink() bool { - return te.mode == EntryModeSymlink + return te.gogitTreeEntry.Mode == filemode.Symlink +} + +// IsRegular if the entry is a regular file +func (te *TreeEntry) IsRegular() bool { + return te.gogitTreeEntry.Mode == filemode.Regular } // Blob retrun the blob object the entry @@ -140,18 +143,18 @@ func (te *TreeEntry) GetSubJumpablePathName() string { if te.IsSubModule() || !te.IsDir() { return "" } - tree, err := te.ptree.SubTree(te.name) + tree, err := te.ptree.SubTree(te.Name()) if err != nil { - return te.name + return te.Name() } entries, _ := tree.ListEntries() if len(entries) == 1 && entries[0].IsDir() { name := entries[0].GetSubJumpablePathName() if name != "" { - return te.name + "/" + name + return te.Name() + "/" + name } } - return te.name + return te.Name() } // Entries a list of entry @@ -167,7 +170,7 @@ var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{ return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule() }, func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool { - return cmp(t1.name, t2.name) + return cmp(t1.Name(), t2.Name()) }, } From ed4b558f0e2ce766ee6f0394c262d7dd20a1f042 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 01:08:59 +0200 Subject: [PATCH 02/35] Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara --- routers/repo/wiki.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 790214e8e5395..385fe04f8495e 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -57,7 +57,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) return nil, err } for _, entry := range entries { - if entry.Type == git.ObjectBlob && entry.Name() == target { + if entry.IsRegular() && entry.Name() == target { return entry, nil } } @@ -125,7 +125,7 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi } pages := make([]PageMeta, 0, len(entries)) for _, entry := range entries { - if entry.Type != git.ObjectBlob { + if !entry.IsRegular() { continue } wikiName, err := models.WikiFilenameToName(entry.Name()) @@ -259,7 +259,7 @@ func WikiPages(ctx *context.Context) { } pages := make([]PageMeta, 0, len(entries)) for _, entry := range entries { - if entry.Type != git.ObjectBlob { + if !entry.IsRegular() { continue } c, err := wikiRepo.GetCommitByPath(entry.Name()) From 7f21e2f7360dd08874826e777841805b19469be4 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 01:35:51 +0200 Subject: [PATCH 03/35] Use the treePath to optimize commit info search. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 6e1f0e0f0b11f..af97099e50adc 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -5,8 +5,6 @@ package git import ( - "path" - "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/storer" @@ -16,7 +14,7 @@ import ( func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, error) { entryPaths := make([]string, len(tes)) for i, entry := range tes { - entryPaths[i] = path.Join(treePath, entry.Name()) + entryPaths[i] = entry.Name() } c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID)) @@ -24,7 +22,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom return nil, err } - revs, err := getLastCommitForPaths(c, entryPaths) + revs, err := getLastCommitForPaths(c, treePath, entryPaths) if err != nil { return nil, err } @@ -45,7 +43,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom return commitsInfo, nil } -func getLastCommitForPaths(c *object.Commit, paths []string) ([]*object.Commit, error) { +func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([]*object.Commit, error) { cIter := object.NewCommitIterCTime(c, nil, nil) result := make([]*object.Commit, len(paths)) remainingResults := len(paths) @@ -55,6 +53,14 @@ func getLastCommitForPaths(c *object.Commit, paths []string) ([]*object.Commit, return nil, err } + if treePath != "" { + cTree, err = cTree.Tree(treePath) + if err != nil { + return nil, err + } + } + lastTreeHash := cTree.Hash + currentEntryHashes := make([]plumbing.Hash, len(paths)) for i, path := range paths { cEntry, err := cTree.FindEntry(path) @@ -73,6 +79,21 @@ func getLastCommitForPaths(c *object.Commit, paths []string) ([]*object.Commit, return err } + if treePath != "" { + parentTree, err = parentTree.Tree(treePath) + // the whole tree doesn't exist + if err != nil { + return nil + } + } + + // bail-out early if this tree branch was not changed in the commit + if lastTreeHash == parentTree.Hash { + copy(newEntryHashes, currentEntryHashes) + return nil + } + lastTreeHash = parentTree.Hash + for i, path := range paths { // skip path if we already found it if currentEntryHashes[i] != plumbing.ZeroHash { From 268bb79940b2a689422311216f3a0e8899cbb76f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 02:11:46 +0200 Subject: [PATCH 04/35] Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 45 +++++++++++++++++++++++++++++--------- routers/repo/view.go | 11 ++-------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index af97099e50adc..fcf3813491c0f 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -11,7 +11,7 @@ import ( ) // GetCommitsInfo gets information of all commits that are corresponding to these entries -func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, error) { +func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) { entryPaths := make([]string, len(tes)) for i, entry := range tes { entryPaths[i] = entry.Name() @@ -19,12 +19,12 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID)) if err != nil { - return nil, err + return nil, nil, err } - revs, err := getLastCommitForPaths(c, treePath, entryPaths) + revs, treeCommit, err := getLastCommitForPaths(c, treePath, entryPaths) if err != nil { - return nil, err + return nil, nil, err } commit.repo.gogitStorage.Close() @@ -40,23 +40,45 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom } commitsInfo[i] = []interface{}{entry, commit} } - return commitsInfo, nil + return commitsInfo, convertCommit(treeCommit), nil } -func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([]*object.Commit, error) { +func convertCommit(c *object.Commit) *Commit { + commiter := Signature(c.Committer) + author := Signature(c.Author) + + var pgpSignaure *CommitGPGSignature + if c.PGPSignature != "" { + pgpSignaure = &CommitGPGSignature{ + Signature: c.PGPSignature, + Payload: c.Message, // FIXME: This is not correct + } + } + + return &Commit{ + ID: SHA1(c.Hash), + CommitMessage: c.Message, + Committer: &commiter, + Author: &author, + Signature: pgpSignaure, + } +} + +func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([]*object.Commit, *object.Commit, error) { cIter := object.NewCommitIterCTime(c, nil, nil) result := make([]*object.Commit, len(paths)) + var resultTree *object.Commit remainingResults := len(paths) cTree, err := c.Tree() if err != nil { - return nil, err + return nil, nil, err } if treePath != "" { cTree, err = cTree.Tree(treePath) if err != nil { - return nil, err + return nil, nil, err } } lastTreeHash := cTree.Hash @@ -65,7 +87,7 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([ for i, path := range paths { cEntry, err := cTree.FindEntry(path) if err != nil { - return nil, err + return nil, nil, err } currentEntryHashes[i] = cEntry.Hash } @@ -91,6 +113,9 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([ if lastTreeHash == parentTree.Hash { copy(newEntryHashes, currentEntryHashes) return nil + } else if resultTree == nil { + // save the latest commit that updated treePath + resultTree = current } lastTreeHash = parentTree.Hash @@ -136,5 +161,5 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([ return nil }) - return result, nil + return result, resultTree, nil } diff --git a/routers/repo/view.go b/routers/repo/view.go index e883ebbf89de2..d20f94dfba629 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -49,7 +49,8 @@ func renderDirectory(ctx *context.Context, treeLink string) { } entries.CustomSort(base.NaturalSortLess) - ctx.Data["Files"], err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, nil) + var latestCommit *git.Commit + ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, nil) if err != nil { ctx.ServerError("GetCommitsInfo", err) return @@ -178,14 +179,6 @@ func renderDirectory(ctx *context.Context, treeLink string) { // Show latest commit info of repository in table header, // or of directory if not in root directory. - latestCommit := ctx.Repo.Commit - if len(ctx.Repo.TreePath) > 0 { - latestCommit, err = ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath) - if err != nil { - ctx.ServerError("GetCommitByPath", err) - return - } - } ctx.Data["LatestCommit"] = latestCommit ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit) ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) From abab5aeeade4001b19eb570909cf3d21dcbcdd57 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 02:28:08 +0200 Subject: [PATCH 05/35] Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index fcf3813491c0f..d7e2e80ed3e26 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -105,6 +105,9 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([ parentTree, err = parentTree.Tree(treePath) // the whole tree doesn't exist if err != nil { + if resultTree == nil { + resultTree = current + } return nil } } From e4f68c669492d0a9b3d6e5ca3338d19872cd07d4 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 03:58:08 +0200 Subject: [PATCH 06/35] Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara --- modules/git/commit_info.go | 6 ++ modules/git/repo.go | 4 +- modules/git/repo_branch.go | 28 ++++----- modules/git/repo_commit.go | 119 +++++++------------------------------ modules/git/repo_tag.go | 36 +++++------ modules/git/repo_tree.go | 15 ++--- 6 files changed, 68 insertions(+), 140 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index d7e2e80ed3e26..da68a68512eea 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -55,12 +55,18 @@ func convertCommit(c *object.Commit) *Commit { } } + parents := make([]SHA1, len(c.ParentHashes)) + for i, parentHash := range c.ParentHashes { + parents[i] = SHA1(parentHash) + } + return &Commit{ ID: SHA1(c.Hash), CommitMessage: c.Message, Committer: &commiter, Author: &author, Signature: pgpSignaure, + parents: parents, } } diff --git a/modules/git/repo.go b/modules/git/repo.go index 252a84f3d18f4..f2cb17f35e0bf 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -27,8 +27,7 @@ import ( type Repository struct { Path string - commitCache *ObjectCache - tagCache *ObjectCache + tagCache *ObjectCache gogitRepo *gogit.Repository gogitStorage *filesystem.Storage @@ -96,7 +95,6 @@ func OpenRepository(repoPath string) (*Repository, error) { Path: repoPath, gogitRepo: gogitRepo, gogitStorage: storage, - commitCache: newObjectCache(), tagCache: newObjectCache(), }, nil } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 6414abbec57b7..bce80c1b0882f 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -9,7 +9,6 @@ import ( "fmt" "strings" - "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" ) @@ -29,7 +28,11 @@ func IsBranchExist(repoPath, name string) bool { // IsBranchExist returns true if given branch exists in current repository. func (repo *Repository) IsBranchExist(name string) bool { - return IsBranchExist(repo.Path, name) + _, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true) + if err != nil { + return false + } + return true } // Branch represents a Git branch. @@ -64,24 +67,21 @@ func (repo *Repository) SetDefaultBranch(name string) error { // GetBranches returns all branches of the repository. func (repo *Repository) GetBranches() ([]string, error) { - r, err := git.PlainOpen(repo.Path) - if err != nil { - return nil, err - } + var branchNames []string - branchIter, err := r.Branches() + branches, err := repo.gogitRepo.Branches() if err != nil { return nil, err } - branches := make([]string, 0) - if err = branchIter.ForEach(func(branch *plumbing.Reference) error { - branches = append(branches, branch.Name().Short()) + + branches.ForEach(func(branch *plumbing.Reference) error { + branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix)) return nil - }); err != nil { - return nil, err - } + }) + + // TODO: Sort? - return branches, nil + return branchNames, nil } // DeleteBranchOptions Option(s) for delete branch diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 1ecd1f88914e2..9f30ce9337c86 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -10,19 +10,19 @@ import ( "strconv" "strings" - version "github.com/mcuadros/go-version" + "github.com/mcuadros/go-version" + + "gopkg.in/src-d/go-git.v4/plumbing" ) // GetRefCommitID returns the last commit ID string of given reference (branch or tag). func (repo *Repository) GetRefCommitID(name string) (string, error) { - stdout, err := NewCommand("show-ref", "--verify", name).RunInDir(repo.Path) + ref, err := repo.gogitRepo.Reference(plumbing.ReferenceName(name), true) if err != nil { - if strings.Contains(err.Error(), "not a valid ref") { - return "", ErrNotExist{name, ""} - } return "", err } - return strings.Split(stdout, " ")[0], nil + + return ref.Hash().String(), nil } // GetBranchCommitID returns last commit ID string of given branch. @@ -42,114 +42,35 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { return strings.TrimSpace(stdout), nil } -// parseCommitData parses commit information from the (uncompressed) raw -// data from the commit object. -// \n\n separate headers from message -func parseCommitData(data []byte) (*Commit, error) { - commit := new(Commit) - commit.parents = make([]SHA1, 0, 1) - // we now have the contents of the commit object. Let's investigate... - nextline := 0 -l: - for { - eol := bytes.IndexByte(data[nextline:], '\n') - switch { - case eol > 0: - line := data[nextline : nextline+eol] - spacepos := bytes.IndexByte(line, ' ') - reftype := line[:spacepos] - switch string(reftype) { - case "tree", "object": - id, err := NewIDFromString(string(line[spacepos+1:])) - if err != nil { - return nil, err - } - commit.Tree.ID = id - case "parent": - // A commit can have one or more parents - oid, err := NewIDFromString(string(line[spacepos+1:])) - if err != nil { - return nil, err - } - commit.parents = append(commit.parents, oid) - case "author", "tagger": - sig, err := newSignatureFromCommitline(line[spacepos+1:]) - if err != nil { - return nil, err - } - commit.Author = sig - case "committer": - sig, err := newSignatureFromCommitline(line[spacepos+1:]) - if err != nil { - return nil, err - } - commit.Committer = sig - case "gpgsig": - sig, err := newGPGSignatureFromCommitline(data, nextline+spacepos+1, false) - if err != nil { - return nil, err - } - commit.Signature = sig - } - nextline += eol + 1 - case eol == 0: - cm := string(data[nextline+1:]) - - // Tag GPG signatures are stored below the commit message - sigindex := strings.Index(cm, "-----BEGIN PGP SIGNATURE-----") - if sigindex != -1 { - sig, err := newGPGSignatureFromCommitline(data, (nextline+1)+sigindex, true) - if err == nil && sig != nil { - // remove signature from commit message - if sigindex == 0 { - cm = "" - } else { - cm = cm[:sigindex-1] - } - commit.Signature = sig - } - } - - commit.CommitMessage = cm - break l - default: - break l - } - } - return commit, nil -} - func (repo *Repository) getCommit(id SHA1) (*Commit, error) { - c, ok := repo.commitCache.Get(id.String()) - if ok { - log("Hit cache: %s", id) - return c.(*Commit), nil - } + //c, ok := repo.commitCache.Get(id.String()) + //if ok { + // log("Hit cache: %s", id) + // return c.(*Commit), nil + //} - data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path) + gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) if err != nil { - if strings.Contains(err.Error(), "fatal: Not a valid object name") { - return nil, ErrNotExist{id.String(), ""} - } return nil, err } - commit, err := parseCommitData(data) - if err != nil { - return nil, err - } + commit := convertCommit(gogitCommit) commit.repo = repo - commit.ID = id - data, err = NewCommand("name-rev", id.String()).RunInDirBytes(repo.Path) + if tree, err := gogitCommit.Tree(); err != nil { + commit.Tree.ID = SHA1(tree.Hash) + commit.Tree.gogitTree = tree + } + + data, err := NewCommand("name-rev", id.String()).RunInDirBytes(repo.Path) if err != nil { return nil, err } // name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12" commit.Branch = strings.Split(strings.Split(string(data), " ")[1], "~")[0] + //repo.commitCache.Set(id.String(), commit) - repo.commitCache.Set(id.String(), commit) return commit, nil } diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 84825d7dc3568..fdeb7f9715baf 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/mcuadros/go-version" + "gopkg.in/src-d/go-git.v4/plumbing" ) // TagPrefix tags prefix path on the repository @@ -20,7 +21,11 @@ func IsTagExist(repoPath, name string) bool { // IsTagExist returns true if given tag exists in the repository. func (repo *Repository) IsTagExist(name string) bool { - return IsTagExist(repo.Path, name) + _, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true) + if err != nil { + return false + } + return true } // CreateTag create one tag in the repository @@ -122,28 +127,25 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) { // GetTags returns all tags of the repository. func (repo *Repository) GetTags() ([]string, error) { - cmd := NewCommand("tag", "-l") - if version.Compare(gitVersion, "2.0.0", ">=") { - cmd.AddArguments("--sort=-v:refname") - } + var tagNames []string - stdout, err := cmd.RunInDir(repo.Path) + tags, err := repo.gogitRepo.Tags() if err != nil { - return nil, err + return nil, nil } - tags := strings.Split(stdout, "\n") - tags = tags[:len(tags)-1] + tags.ForEach(func(tag *plumbing.Reference) error { + tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix)) + return nil + }) - if version.Compare(gitVersion, "2.0.0", "<") { - version.Sort(tags) + version.Sort(tagNames) - // Reverse order - for i := 0; i < len(tags)/2; i++ { - j := len(tags) - i - 1 - tags[i], tags[j] = tags[j], tags[i] - } + // Reverse order + for i := 0; i < len(tagNames)/2; i++ { + j := len(tagNames) - i - 1 + tagNames[i], tagNames[j] = tagNames[j], tagNames[i] } - return tags, nil + return tagNames, nil } diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 3fa491d529ce7..4e1a337a5b085 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -4,16 +4,17 @@ package git +import "gopkg.in/src-d/go-git.v4/plumbing" + func (repo *Repository) getTree(id SHA1) (*Tree, error) { - treePath := filepathFromSHA1(repo.Path, id.String()) - if isFile(treePath) { - _, err := NewCommand("ls-tree", id.String()).RunInDir(repo.Path) - if err != nil { - return nil, ErrNotExist{id.String(), ""} - } + gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id)) + if err != nil { + return nil, err } - return NewTree(repo, id), nil + tree := NewTree(repo, id) + tree.gogitTree = gogitTree + return tree, nil } // GetTree find the tree object in the repository. From 403596e925ebb67f1b2a68c26266801c47da8f14 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 04:17:10 +0200 Subject: [PATCH 07/35] Use go-git for reading blobs. Signed-off-by: Filip Navara --- modules/context/repo.go | 3 ++- modules/git/blob.go | 53 +++------------------------------------ modules/git/commit.go | 4 ++- modules/git/tree_entry.go | 3 ++- routers/repo/editor.go | 3 ++- routers/repo/issue.go | 5 ++-- routers/repo/wiki.go | 3 ++- 7 files changed, 16 insertions(+), 58 deletions(-) diff --git a/modules/context/repo.go b/modules/context/repo.go index 0cbd89a75f44b..12a2be31df691 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -142,10 +142,11 @@ func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) { if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize { return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"} } - reader, err := treeEntry.Blob().Data() + reader, err := treeEntry.Blob().DataAsync() if err != nil { return nil, err } + defer reader.Close() data, err := ioutil.ReadAll(reader) if err != nil { return nil, err diff --git a/modules/git/blob.go b/modules/git/blob.go index a6e392eeb50a6..847450c01cfad 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -5,12 +5,7 @@ package git import ( - "bytes" - "fmt" "io" - "io/ioutil" - "os" - "os/exec" ) // Blob represents a Git object. @@ -19,55 +14,13 @@ type Blob struct { *TreeEntry } -// Data gets content of blob all at once and wrap it as io.Reader. -// This can be very slow and memory consuming for huge content. -func (b *Blob) Data() (io.Reader, error) { - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - - // Preallocate memory to save ~50% memory usage on big files. - stdout.Grow(int(b.Size() + 2048)) - - if err := b.DataPipeline(stdout, stderr); err != nil { - return nil, concatenateError(err, stderr.String()) - } - return stdout, nil -} - -// DataPipeline gets content of blob and write the result or error to stdout or stderr -func (b *Blob) DataPipeline(stdout, stderr io.Writer) error { - return NewCommand("show", b.ID.String()).RunInDirPipeline(b.repo.Path, stdout, stderr) -} - -type cmdReadCloser struct { - cmd *exec.Cmd - stdout io.Reader -} - -func (c cmdReadCloser) Read(p []byte) (int, error) { - return c.stdout.Read(p) -} - -func (c cmdReadCloser) Close() error { - io.Copy(ioutil.Discard, c.stdout) - return c.cmd.Wait() -} - // DataAsync gets a ReadCloser for the contents of a blob without reading it all. // Calling the Close function on the result will discard all unread output. func (b *Blob) DataAsync() (io.ReadCloser, error) { - cmd := exec.Command("git", "show", b.ID.String()) - cmd.Dir = b.repo.Path - cmd.Stderr = os.Stderr - - stdout, err := cmd.StdoutPipe() + gogitBlob, err := b.repo.gogitRepo.BlobObject(b.gogitTreeEntry.Hash) if err != nil { - return nil, fmt.Errorf("StdoutPipe: %v", err) - } - - if err = cmd.Start(); err != nil { - return nil, fmt.Errorf("Start: %v", err) + return nil, err } - return cmdReadCloser{stdout: stdout, cmd: cmd}, nil + return gogitBlob.Reader() } diff --git a/modules/git/commit.go b/modules/git/commit.go index 85c9554bb534e..516d5addeb81a 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -276,11 +276,13 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) { } return nil, err } - rd, err := entry.Blob().Data() + + rd, err := entry.Blob().DataAsync() if err != nil { return nil, err } + defer rd.Close() scanner := bufio.NewScanner(rd) c.submoduleCache = newObjectCache() var ismodule bool diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 910ffb5de0b1d..43ee0247bb697 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -106,10 +106,11 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) { } // read the link - r, err := te.Blob().Data() + r, err := te.Blob().DataAsync() if err != nil { return nil, err } + defer r.Close() buf := make([]byte, te.Size()) _, err = io.ReadFull(r, buf) if err != nil { diff --git a/routers/repo/editor.go b/routers/repo/editor.go index 6c47f51f9b9ed..17250afb6cb1e 100644 --- a/routers/repo/editor.go +++ b/routers/repo/editor.go @@ -94,11 +94,12 @@ func editFile(ctx *context.Context, isNewFile bool) { return } - dataRc, err := blob.Data() + dataRc, err := blob.DataAsync() if err != nil { ctx.NotFound("blob.Data", err) return } + defer dataRc.Close() ctx.Data["FileSize"] = blob.Size() ctx.Data["FileName"] = blob.Name() diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 42f3ddf4e8559..7d235d84ef976 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -9,7 +9,6 @@ import ( "bytes" "errors" "fmt" - "io" "io/ioutil" "net/http" "strconv" @@ -363,7 +362,6 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models. } func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) { - var r io.Reader var bytes []byte if ctx.Repo.Commit == nil { @@ -381,10 +379,11 @@ func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (str if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize { return "", false } - r, err = entry.Blob().Data() + r, err := entry.Blob().DataAsync() if err != nil { return "", false } + defer r.Close() bytes, err = ioutil.ReadAll(r) if err != nil { return "", false diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 385fe04f8495e..c0fb370dd98ec 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -81,11 +81,12 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err // wikiContentsByEntry returns the contents of the wiki page referenced by the // given tree entry. Writes to ctx if an error occurs. func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { - reader, err := entry.Blob().Data() + reader, err := entry.Blob().DataAsync() if err != nil { ctx.ServerError("Blob.Data", err) return nil } + defer reader.Close() content, err := ioutil.ReadAll(reader) if err != nil { ctx.ServerError("ReadAll", err) From 8a5ab2368d7801538d0a2c9a13429ba0ee2008c3 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 11:48:40 +0200 Subject: [PATCH 08/35] Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 11 +++-------- modules/git/repo_commit.go | 2 +- modules/git/sha1.go | 26 +------------------------- modules/git/tree.go | 2 +- 4 files changed, 6 insertions(+), 35 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index da68a68512eea..df01e6b5af8fb 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -32,7 +32,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom commitsInfo := make([][]interface{}, len(tes)) for i, entry := range tes { commit := &Commit{ - ID: SHA1(revs[i].Hash), + ID: revs[i].Hash, CommitMessage: revs[i].Message, Committer: &Signature{ When: revs[i].Committer.When, @@ -55,18 +55,13 @@ func convertCommit(c *object.Commit) *Commit { } } - parents := make([]SHA1, len(c.ParentHashes)) - for i, parentHash := range c.ParentHashes { - parents[i] = SHA1(parentHash) - } - return &Commit{ - ID: SHA1(c.Hash), + ID: c.Hash, CommitMessage: c.Message, Committer: &commiter, Author: &author, Signature: pgpSignaure, - parents: parents, + parents: c.ParentHashes, } } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 9f30ce9337c86..f8e14f034774c 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -58,7 +58,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { commit.repo = repo if tree, err := gogitCommit.Tree(); err != nil { - commit.Tree.ID = SHA1(tree.Hash) + commit.Tree.ID = tree.Hash commit.Tree.gogitTree = tree } diff --git a/modules/git/sha1.go b/modules/git/sha1.go index 582cb3c2dfd78..bee4801437387 100644 --- a/modules/git/sha1.go +++ b/modules/git/sha1.go @@ -5,7 +5,6 @@ package git import ( - "bytes" "encoding/hex" "fmt" "strings" @@ -17,30 +16,7 @@ import ( const EmptySHA = "0000000000000000000000000000000000000000" // SHA1 a git commit name -type SHA1 plumbing.Hash - -// Equal returns true if s has the same SHA1 as caller. -// Support 40-length-string, []byte, SHA1. -func (id SHA1) Equal(s2 interface{}) bool { - switch v := s2.(type) { - case string: - if len(v) != 40 { - return false - } - return v == id.String() - case []byte: - return bytes.Equal(v, id[:]) - case SHA1: - return v == id - default: - return false - } -} - -// String returns string (hex) representation of the Oid. -func (id SHA1) String() string { - return hex.EncodeToString(id[:]) -} +type SHA1 = plumbing.Hash // MustID always creates a new SHA1 from a [20]byte array with no validation of input. func MustID(b []byte) SHA1 { diff --git a/modules/git/tree.go b/modules/git/tree.go index 98deced83ab09..c334a799e29fd 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -74,7 +74,7 @@ func (t *Tree) ListEntries() (Entries, error) { entries := make([]*TreeEntry, len(t.gogitTree.Entries)) for i, entry := range t.gogitTree.Entries { entries[i] = &TreeEntry{ - ID: SHA1(entry.Hash), + ID: entry.Hash, gogitTreeEntry: &t.gogitTree.Entries[i], ptree: t, } From 77996d6cad9ad01f29807ac5aa28bba1f3885b61 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 11:57:56 +0200 Subject: [PATCH 09/35] Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 7 ++----- modules/git/signature.go | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index df01e6b5af8fb..3ab236947429f 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -44,9 +44,6 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom } func convertCommit(c *object.Commit) *Commit { - commiter := Signature(c.Committer) - author := Signature(c.Author) - var pgpSignaure *CommitGPGSignature if c.PGPSignature != "" { pgpSignaure = &CommitGPGSignature{ @@ -58,8 +55,8 @@ func convertCommit(c *object.Commit) *Commit { return &Commit{ ID: c.Hash, CommitMessage: c.Message, - Committer: &commiter, - Author: &author, + Committer: &c.Committer, + Author: &c.Author, Signature: pgpSignaure, parents: c.ParentHashes, } diff --git a/modules/git/signature.go b/modules/git/signature.go index e73f908e9a536..69b096ed65c04 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -13,7 +13,7 @@ import ( ) // Signature represents the Author or Committer information. -type Signature object.Signature +type Signature = object.Signature const ( // GitTimeLayout is the (default) time layout used by git. From f64b5d52e070ce42d8d46127024a21bbd8d43fbe Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 24 Sep 2018 23:08:22 +0200 Subject: [PATCH 10/35] Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 3ab236947429f..216d265134ff1 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -162,5 +162,10 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([ return nil }) + // only one commit in the repository + if resultTree == nil { + resultTree = c + } + return result, resultTree, nil } From 97501ea79932ad8318f105a74a11868429d89b16 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 25 Sep 2018 09:16:19 +0200 Subject: [PATCH 11/35] Fix PGP signature verification. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 53 +++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 216d265134ff1..e4e2fc32d30ee 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -5,6 +5,9 @@ package git import ( + "fmt" + "strings" + "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/storer" @@ -43,21 +46,57 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom return commitsInfo, convertCommit(treeCommit), nil } -func convertCommit(c *object.Commit) *Commit { - var pgpSignaure *CommitGPGSignature - if c.PGPSignature != "" { - pgpSignaure = &CommitGPGSignature{ - Signature: c.PGPSignature, - Payload: c.Message, // FIXME: This is not correct +func convertPGPSignature(c *object.Commit) *CommitGPGSignature { + if c.PGPSignature == "" { + return nil + } + + var w strings.Builder + var err error + + if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil { + return nil + } + + for _, parent := range c.ParentHashes { + if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil { + return nil } } + if _, err = fmt.Fprint(&w, "author "); err != nil { + return nil + } + + if err = c.Author.Encode(&w); err != nil { + return nil + } + + if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil { + return nil + } + + if err = c.Committer.Encode(&w); err != nil { + return nil + } + + if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { + return nil + } + + return &CommitGPGSignature{ + Signature: c.PGPSignature, + Payload: w.String(), + } +} + +func convertCommit(c *object.Commit) *Commit { return &Commit{ ID: c.Hash, CommitMessage: c.Message, Committer: &c.Committer, Author: &c.Author, - Signature: pgpSignaure, + Signature: convertPGPSignature(c), parents: c.ParentHashes, } } From cced8c1febd90837cd61557be93f08ba2e26a026 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 27 Sep 2018 12:22:44 +0200 Subject: [PATCH 12/35] Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara --- modules/git/commit.go | 57 ++++++++ modules/git/commit_info.go | 285 ++++++++++++++++++++----------------- 2 files changed, 208 insertions(+), 134 deletions(-) diff --git a/modules/git/commit.go b/modules/git/commit.go index 516d5addeb81a..b0a83d08e2dc6 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -14,6 +14,8 @@ import ( "net/http" "strconv" "strings" + + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // Commit represents a git commit. @@ -52,6 +54,61 @@ func newGPGSignatureFromCommitline(data []byte, signatureStart int, tag bool) (* return sig, nil } +func convertPGPSignature(c *object.Commit) *CommitGPGSignature { + if c.PGPSignature == "" { + return nil + } + + var w strings.Builder + var err error + + if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil { + return nil + } + + for _, parent := range c.ParentHashes { + if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil { + return nil + } + } + + if _, err = fmt.Fprint(&w, "author "); err != nil { + return nil + } + + if err = c.Author.Encode(&w); err != nil { + return nil + } + + if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil { + return nil + } + + if err = c.Committer.Encode(&w); err != nil { + return nil + } + + if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { + return nil + } + + return &CommitGPGSignature{ + Signature: c.PGPSignature, + Payload: w.String(), + } +} + +func convertCommit(c *object.Commit) *Commit { + return &Commit{ + ID: c.Hash, + CommitMessage: c.Message, + Committer: &c.Committer, + Author: &c.Author, + Signature: convertPGPSignature(c), + parents: c.ParentHashes, + } +} + // Message returns the commit message. Same as retrieving CommitMessage directly. func (c *Commit) Message() string { return c.CommitMessage diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index e4e2fc32d30ee..e0e245526aeb7 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -5,19 +5,17 @@ package git import ( - "fmt" - "strings" - + "github.com/emirpasic/gods/trees/binaryheap" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" - "gopkg.in/src-d/go-git.v4/plumbing/storer" ) // GetCommitsInfo gets information of all commits that are corresponding to these entries func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) { - entryPaths := make([]string, len(tes)) + entryPaths := make([]string, len(tes)+1) + entryPaths[0] = "" for i, entry := range tes { - entryPaths[i] = entry.Name() + entryPaths[i+1] = entry.Name() } c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID)) @@ -25,7 +23,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom return nil, nil, err } - revs, treeCommit, err := getLastCommitForPaths(c, treePath, entryPaths) + revs, err := getLastCommitForPaths(c, treePath, entryPaths) if err != nil { return nil, nil, err } @@ -34,177 +32,196 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom commitsInfo := make([][]interface{}, len(tes)) for i, entry := range tes { - commit := &Commit{ - ID: revs[i].Hash, - CommitMessage: revs[i].Message, - Committer: &Signature{ - When: revs[i].Committer.When, - }, + if rev, ok := revs[entry.Name()]; ok { + commit := convertCommit(rev) + commitsInfo[i] = []interface{}{entry, commit} + } else { + commitsInfo[i] = []interface{}{entry, nil} } - commitsInfo[i] = []interface{}{entry, commit} } - return commitsInfo, convertCommit(treeCommit), nil -} -func convertPGPSignature(c *object.Commit) *CommitGPGSignature { - if c.PGPSignature == "" { - return nil + var treeCommit *Commit + if rev, ok := revs[""]; ok { + treeCommit = convertCommit(rev) } + return commitsInfo, treeCommit, nil +} - var w strings.Builder - var err error +type commitAndPaths struct { + commit *object.Commit + // Paths that are still on the branch represented by commit + paths []string + // Set of hashes for the paths + hashes map[string]plumbing.Hash +} - if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil { - return nil +func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) { + tree, err := c.Tree() + if err != nil { + return nil, err } - for _, parent := range c.ParentHashes { - if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil { - return nil + // Optimize deep traversals by focusing only on the specific tree + if treePath != "" { + tree, err = tree.Tree(treePath) + if err != nil { + return nil, err } } - if _, err = fmt.Fprint(&w, "author "); err != nil { - return nil - } - - if err = c.Author.Encode(&w); err != nil { - return nil - } + return tree, nil +} - if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil { - return nil +func getFullPath(treePath, path string) string { + if treePath != "" { + if path != "" { + return treePath + "/" + path + } else { + return treePath + } } + return path +} - if err = c.Committer.Encode(&w); err != nil { - return nil +func getFileHashes(c *object.Commit, treePath string, paths []string) (map[string]plumbing.Hash, error) { + tree, err := getCommitTree(c, treePath) + if err == object.ErrDirectoryNotFound { + // The whole tree didn't exist, so return empty map + return make(map[string]plumbing.Hash), nil } - - if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { - return nil + if err != nil { + return nil, err } - return &CommitGPGSignature{ - Signature: c.PGPSignature, - Payload: w.String(), + hashes := make(map[string]plumbing.Hash) + for _, path := range paths { + if path != "" { + entry, err := tree.FindEntry(path) + if err == nil { + hashes[path] = entry.Hash + } + } else { + hashes[path] = tree.Hash + } } -} -func convertCommit(c *object.Commit) *Commit { - return &Commit{ - ID: c.Hash, - CommitMessage: c.Message, - Committer: &c.Committer, - Author: &c.Author, - Signature: convertPGPSignature(c), - parents: c.ParentHashes, - } + return hashes, nil } -func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) ([]*object.Commit, *object.Commit, error) { - cIter := object.NewCommitIterCTime(c, nil, nil) - result := make([]*object.Commit, len(paths)) - var resultTree *object.Commit - remainingResults := len(paths) +func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) { + // We do a tree traversal with nodes sorted by commit time + seen := make(map[plumbing.Hash]bool) + heap := binaryheap.NewWith(func(a, b interface{}) int { + if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { + return 1 + } + return -1 + }) - cTree, err := c.Tree() + result := make(map[string]*object.Commit) + initialHashes, err := getFileHashes(c, treePath, paths) if err != nil { - return nil, nil, err + return nil, err } - if treePath != "" { - cTree, err = cTree.Tree(treePath) - if err != nil { - return nil, nil, err - } - } - lastTreeHash := cTree.Hash + // Start search from the root commit and with full set of paths + heap.Push(&commitAndPaths{c, paths, initialHashes}) - currentEntryHashes := make([]plumbing.Hash, len(paths)) - for i, path := range paths { - cEntry, err := cTree.FindEntry(path) - if err != nil { - return nil, nil, err + for { + cIn, ok := heap.Pop() + if !ok { + break } - currentEntryHashes[i] = cEntry.Hash - } + current := cIn.(*commitAndPaths) + currentID := current.commit.ID() - cIter.ForEach(func(current *object.Commit) error { - newEntryHashes := make([]plumbing.Hash, len(paths)) + if seen[currentID] { + continue + } + seen[currentID] = true - err := current.Parents().ForEach(func(parent *object.Commit) error { - parentTree, err := parent.Tree() + // Load the parent commits for the one we are currently examining + numParents := current.commit.NumParents() + var parents []*object.Commit + for i := 0; i < numParents; i++ { + parent, err := current.commit.Parent(i) if err != nil { - return err + break } + parents = append(parents, parent) + } - if treePath != "" { - parentTree, err = parentTree.Tree(treePath) - // the whole tree doesn't exist - if err != nil { - if resultTree == nil { - resultTree = current - } - return nil - } + // Examine the current commit and set of interesting paths + numOfParentsWithPath := make([]int, len(current.paths)) + pathChanged := make([]bool, len(current.paths)) + parentHashes := make([]map[string]plumbing.Hash, len(parents)) + for j, parent := range parents { + parentHashes[j], err = getFileHashes(parent, treePath, current.paths) + if err != nil { + break } - // bail-out early if this tree branch was not changed in the commit - if lastTreeHash == parentTree.Hash { - copy(newEntryHashes, currentEntryHashes) - return nil - } else if resultTree == nil { - // save the latest commit that updated treePath - resultTree = current - } - lastTreeHash = parentTree.Hash - - for i, path := range paths { - // skip path if we already found it - if currentEntryHashes[i] != plumbing.ZeroHash { - // find parents that contain the path - if parentEntry, err := parentTree.FindEntry(path); err == nil { - // if the hash for the path differs in the parent then the current commit changed it - if parentEntry.Hash == currentEntryHashes[i] { - newEntryHashes[i] = currentEntryHashes[i] - } else { - // mark for saving the result below - newEntryHashes[i] = plumbing.ZeroHash - // stop any further processing for this file - currentEntryHashes[i] = plumbing.ZeroHash - } + for i, path := range current.paths { + if parentHashes[j][path] != plumbing.ZeroHash { + numOfParentsWithPath[i]++ + if parentHashes[j][path] != current.hashes[path] { + pathChanged[i] = true } } } - - return nil - }) - if err != nil { - return err } - // if a file didn't exist in any parent commit then it must have been created in the - // current one. also we mark changed files in the loop above as not present in the - // parent to simplify processing - for i, newEntryHash := range newEntryHashes { - if newEntryHash == plumbing.ZeroHash && result[i] == nil { - result[i] = current - remainingResults-- + var remainingPaths []string + for i, path := range current.paths { + switch numOfParentsWithPath[i] { + case 0: + // The path didn't exist in any parent, so it must have been created by + // this commit. The results could already contain some newer change from + // different path, so don't override that. + if result[path] == nil { + result[path] = current.commit + } + case 1: + // The file is present on exactly one parent, so check if it was changed + // and save the revision if it did. + if pathChanged[i] { + if result[path] == nil { + result[path] = current.commit + } + } else { + remainingPaths = append(remainingPaths, path) + } + default: + // The file is present on more than one of the parent paths, so this is + // a merge. We have to examine all the parent trees to find out where + // the change occured. pathChanged[i] would tell us that the file was + // changed during the merge, but it wouldn't tell us the relevant commit + // that introduced it. + remainingPaths = append(remainingPaths, path) } } - if remainingResults == 0 { - return storer.ErrStop - } + if len(remainingPaths) > 0 { + // Add the parent nodes along with remaining paths to the heap for further + // processing. + for j, parent := range parents { + if seen[parent.ID()] { + continue + } - currentEntryHashes = newEntryHashes - return nil - }) + // Combine remainingPath with paths available on the parent branch + // and make union of them + var remainingPathsForParent []string + for _, path := range remainingPaths { + if parentHashes[j][path] != plumbing.ZeroHash { + remainingPathsForParent = append(remainingPathsForParent, path) + } + } - // only one commit in the repository - if resultTree == nil { - resultTree = c + heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]}) + } + } } - return result, resultTree, nil + return result, nil } From e121188d8b239acb7d2e517eea1b84c708e047ac Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 1 Oct 2018 10:46:02 +0200 Subject: [PATCH 13/35] Fix typo in condition. Signed-off-by: Filip Navara --- modules/git/repo_commit.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index f8e14f034774c..a244f12775405 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -57,11 +57,14 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { commit := convertCommit(gogitCommit) commit.repo = repo - if tree, err := gogitCommit.Tree(); err != nil { - commit.Tree.ID = tree.Hash - commit.Tree.gogitTree = tree + tree, err := gogitCommit.Tree() + if err != nil { + return nil, err } + commit.Tree.ID = tree.Hash + commit.Tree.gogitTree = tree + data, err := NewCommand("name-rev", id.String()).RunInDirBytes(repo.Path) if err != nil { return nil, err From 9eef362dfc4f217b99569f67cf1b3f31e2bf1e9f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 18 Oct 2018 20:11:17 +0200 Subject: [PATCH 14/35] Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara --- models/repo_branch.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/models/repo_branch.go b/models/repo_branch.go index 1c62a3d67d66d..c61a06e663f0c 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -90,6 +90,8 @@ func (repo *Repository) DeleteLocalBranch(branchName string) error { type Branch struct { Path string Name string + + gitRepo *git.Repository } // GetBranchesByPath returns a branch by it's path @@ -107,8 +109,9 @@ func GetBranchesByPath(path string) ([]*Branch, error) { branches := make([]*Branch, len(brs)) for i := range brs { branches[i] = &Branch{ - Path: path, - Name: brs[i], + Path: path, + Name: brs[i], + gitRepo: gitRepo, } } return branches, nil @@ -260,9 +263,13 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName // GetCommit returns all the commits of a branch func (branch *Branch) GetCommit() (*git.Commit, error) { - gitRepo, err := git.OpenRepository(branch.Path) - if err != nil { - return nil, err + if branch.gitRepo != nil { + return branch.gitRepo.GetBranchCommit(branch.Name) + } else { + gitRepo, err := git.OpenRepository(branch.Path) + if err != nil { + return nil, err + } + return gitRepo.GetBranchCommit(branch.Name) } - return gitRepo.GetBranchCommit(branch.Name) } From a6155d51ead653157d8f535a3a335e47557cd8a1 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 17 Dec 2018 16:01:47 +0100 Subject: [PATCH 15/35] Fix lising submodules. Signed-off-by: Filip Navara --- modules/git/commit_info.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index e0e245526aeb7..1a787d483a0b6 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -33,8 +33,19 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom commitsInfo := make([][]interface{}, len(tes)) for i, entry := range tes { if rev, ok := revs[entry.Name()]; ok { - commit := convertCommit(rev) - commitsInfo[i] = []interface{}{entry, commit} + entryCommit := convertCommit(rev) + if entry.IsSubModule() { + subModuleURL := "" + if subModule, err := commit.GetSubModule(entry.Name()); err != nil { + return nil, nil, err + } else if subModule != nil { + subModuleURL = subModule.URL + } + subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String()) + commitsInfo[i] = []interface{}{entry, subModuleFile} + } else { + commitsInfo[i] = []interface{}{entry, entryCommit} + } } else { commitsInfo[i] = []interface{}{entry, nil} } From aeb6cafd2e8d6e3cc6e1321f4c680f176e5d2544 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 8 Jan 2019 17:01:50 +0100 Subject: [PATCH 16/35] Fix build Signed-off-by: Filip Navara --- modules/git/commit_info.go | 2 +- modules/git/tree.go | 2 +- modules/git/tree_entry.go | 2 +- routers/api/v1/repo/tree.go | 19 ++++++++++++++----- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 1a787d483a0b6..05a0264ce78b0 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -123,7 +123,7 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m // We do a tree traversal with nodes sorted by commit time seen := make(map[plumbing.Hash]bool) heap := binaryheap.NewWith(func(a, b interface{}) int { - if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { + if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) { return 1 } return -1 diff --git a/modules/git/tree.go b/modules/git/tree.go index c334a799e29fd..2f080840069eb 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -110,7 +110,7 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) { } convertedEntry := &TreeEntry{ - ID: SHA1(entry.Hash), + ID: entry.Hash, gogitTreeEntry: &entry, ptree: t, } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 43ee0247bb697..e2b24e74dbb92 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -50,7 +50,7 @@ func (te *TreeEntry) Name() string { // Mode returns the mode of the entry func (te *TreeEntry) Mode() EntryMode { - return te.mode + return EntryMode(te.gogitTreeEntry.Mode) } // Size returns the size of the entry diff --git a/routers/api/v1/repo/tree.go b/routers/api/v1/repo/tree.go index 012eba673e21e..61984dcdb93d7 100644 --- a/routers/api/v1/repo/tree.go +++ b/routers/api/v1/repo/tree.go @@ -128,11 +128,20 @@ func GetTreeBySHA(ctx *context.APIContext, sha string) *gitea.GitTreeResponse { tree.Entries = make([]gitea.GitEntry, rangeEnd-rangeStart) for e := rangeStart; e < rangeEnd; e++ { i := e - rangeStart - tree.Entries[i].Path = entries[e].Name() - tree.Entries[i].Mode = fmt.Sprintf("%06x", entries[e].Mode()) - tree.Entries[i].Type = string(entries[e].Type) - tree.Entries[i].Size = entries[e].Size() - tree.Entries[i].SHA = entries[e].ID.String() + + tree.Entries[e].Path = entries[e].Name() + tree.Entries[e].Mode = fmt.Sprintf("%06x", entries[e].Mode()) + + if entries[e].Mode() == git.EntryModeCommit { + tree.Entries[e].Type = "commit" + } else if entries[e].Mode() == git.EntryModeTree { + tree.Entries[e].Type = "tree" + } else { + tree.Entries[e].Type = "blob" + } + + tree.Entries[e].Size = entries[e].Size() + tree.Entries[e].SHA = entries[e].ID.String() if entries[e].IsDir() { copy(treeURL[copyPos:], entries[e].ID.String()) From 157380eea2a9dc1e18f4cd451efe8c07ab0a1904 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 1 Apr 2019 11:07:07 +0200 Subject: [PATCH 17/35] Add back commit cache because of name-rev Signed-off-by: Filip Navara --- modules/git/repo.go | 4 +++- modules/git/repo_commit.go | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/git/repo.go b/modules/git/repo.go index f2cb17f35e0bf..252a84f3d18f4 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -27,7 +27,8 @@ import ( type Repository struct { Path string - tagCache *ObjectCache + commitCache *ObjectCache + tagCache *ObjectCache gogitRepo *gogit.Repository gogitStorage *filesystem.Storage @@ -95,6 +96,7 @@ func OpenRepository(repoPath string) (*Repository, error) { Path: repoPath, gogitRepo: gogitRepo, gogitStorage: storage, + commitCache: newObjectCache(), tagCache: newObjectCache(), }, nil } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index a244f12775405..4a9e2111a8eba 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -43,11 +43,11 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { } func (repo *Repository) getCommit(id SHA1) (*Commit, error) { - //c, ok := repo.commitCache.Get(id.String()) - //if ok { - // log("Hit cache: %s", id) - // return c.(*Commit), nil - //} + c, ok := repo.commitCache.Get(id.String()) + if ok { + log("Hit cache: %s", id) + return c.(*Commit), nil + } gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) if err != nil { @@ -72,7 +72,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { // name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12" commit.Branch = strings.Split(strings.Split(string(data), " ")[1], "~")[0] - //repo.commitCache.Set(id.String(), commit) + repo.commitCache.Set(id.String(), commit) return commit, nil } From 10d74c0a59683174fe32554b7048cc9fa9f38b69 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 1 Apr 2019 11:52:03 +0200 Subject: [PATCH 18/35] Fix tests Signed-off-by: Filip Navara --- modules/git/blob_test.go | 17 ++++------------- modules/git/commit_info_test.go | 4 ++-- modules/git/parse_test.go | 32 ++++++++++++++++++++------------ modules/git/repo_blob_test.go | 3 ++- modules/git/tree_entry_test.go | 18 ++++++++++-------- routers/repo/wiki_test.go | 3 ++- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 39516c422ccda..c91fd4cf4cadc 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -5,7 +5,6 @@ package git import ( - "bytes" "io/ioutil" "testing" @@ -50,9 +49,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ` - r, err := testBlob.Data() + r, err := testBlob.DataAsync() assert.NoError(t, err) require.NotNil(t, r) + defer r.Close() data, err := ioutil.ReadAll(r) assert.NoError(t, err) @@ -61,20 +61,11 @@ THE SOFTWARE. func Benchmark_Blob_Data(b *testing.B) { for i := 0; i < b.N; i++ { - r, err := testBlob.Data() + r, err := testBlob.DataAsync() if err != nil { b.Fatal(err) } + defer r.Close() ioutil.ReadAll(r) } } - -func Benchmark_Blob_DataPipeline(b *testing.B) { - stdout := new(bytes.Buffer) - for i := 0; i < b.N; i++ { - stdout.Reset() - if err := testBlob.DataPipeline(stdout, nil); err != nil { - b.Fatal(err) - } - } -} diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go index 120a9a737cb99..d7d863b032ec0 100644 --- a/modules/git/commit_info_test.go +++ b/modules/git/commit_info_test.go @@ -51,7 +51,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) { assert.NoError(t, err) entries, err := tree.ListEntries() assert.NoError(t, err) - commitsInfo, err := entries.GetCommitsInfo(commit, testCase.Path, nil) + commitsInfo, _, err := entries.GetCommitsInfo(commit, testCase.Path, nil) assert.NoError(t, err) assert.Len(t, commitsInfo, len(testCase.ExpectedIDs)) for _, commitInfo := range commitsInfo { @@ -107,7 +107,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) { b.ResetTimer() b.Run(benchmark.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - _, err := entries.GetCommitsInfo(commit, "", nil) + _, _, err := entries.GetCommitsInfo(commit, "", nil) if err != nil { b.Fatal(err) } diff --git a/modules/git/parse_test.go b/modules/git/parse_test.go index 66936cbdf0ad4..d249623949179 100644 --- a/modules/git/parse_test.go +++ b/modules/git/parse_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/src-d/go-git.v4/plumbing/filemode" + "gopkg.in/src-d/go-git.v4/plumbing/object" ) func TestParseTreeEntries(t *testing.T) { @@ -23,10 +25,12 @@ func TestParseTreeEntries(t *testing.T) { Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c\texample/file2.txt\n", Expected: []*TreeEntry{ { - mode: EntryModeBlob, - Type: ObjectBlob, - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), - name: "example/file2.txt", + ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + gogitTreeEntry: &object.TreeEntry{ + Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + Name: "example/file2.txt", + Mode: filemode.Regular, + }, }, }, }, @@ -35,16 +39,20 @@ func TestParseTreeEntries(t *testing.T) { "040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8\texample\n", Expected: []*TreeEntry{ { - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), - Type: ObjectBlob, - mode: EntryModeSymlink, - name: "example/\n.txt", + ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + gogitTreeEntry: &object.TreeEntry{ + Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + Name: "example/\n.txt", + Mode: filemode.Symlink, + }, }, { - ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), - Type: ObjectTree, - mode: EntryModeTree, - name: "example", + ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), + gogitTreeEntry: &object.TreeEntry{ + Hash: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), + Name: "example", + Mode: filemode.Dir, + }, }, }, }, diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go index 074365f16487a..128a227829c1c 100644 --- a/modules/git/repo_blob_test.go +++ b/modules/git/repo_blob_test.go @@ -30,8 +30,9 @@ func TestRepository_GetBlob_Found(t *testing.T) { blob, err := r.GetBlob(testCase.OID) assert.NoError(t, err) - dataReader, err := blob.Data() + dataReader, err := blob.DataAsync() assert.NoError(t, err) + defer dataReader.Close() data, err := ioutil.ReadAll(dataReader) assert.NoError(t, err) diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go index 52920bc00e739..c65a691ecf203 100644 --- a/modules/git/tree_entry_test.go +++ b/modules/git/tree_entry_test.go @@ -8,18 +8,20 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/src-d/go-git.v4/plumbing/filemode" + "gopkg.in/src-d/go-git.v4/plumbing/object" ) func getTestEntries() Entries { return Entries{ - &TreeEntry{name: "v1.0", mode: EntryModeTree}, - &TreeEntry{name: "v2.0", mode: EntryModeTree}, - &TreeEntry{name: "v2.1", mode: EntryModeTree}, - &TreeEntry{name: "v2.12", mode: EntryModeTree}, - &TreeEntry{name: "v2.2", mode: EntryModeTree}, - &TreeEntry{name: "v12.0", mode: EntryModeTree}, - &TreeEntry{name: "abc", mode: EntryModeBlob}, - &TreeEntry{name: "bcd", mode: EntryModeBlob}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v1.0", Mode: filemode.Dir}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.0", Mode: filemode.Dir}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.1", Mode: filemode.Dir}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.12", Mode: filemode.Dir}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.2", Mode: filemode.Dir}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v12.0", Mode: filemode.Dir}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "abc", Mode: filemode.Regular}}, + &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "bcd", Mode: filemode.Regular}}, } } diff --git a/routers/repo/wiki_test.go b/routers/repo/wiki_test.go index 7302f9e48bd07..4687d24f6b28d 100644 --- a/routers/repo/wiki_test.go +++ b/routers/repo/wiki_test.go @@ -40,8 +40,9 @@ func wikiContent(t *testing.T, repo *models.Repository, wikiName string) string if !assert.NotNil(t, entry) { return "" } - reader, err := entry.Blob().Data() + reader, err := entry.Blob().DataAsync() assert.NoError(t, err) + defer reader.Close() bytes, err := ioutil.ReadAll(reader) assert.NoError(t, err) return string(bytes) From 6370c0bcb4df22c25147ed5675b0fd6f2206871f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 1 Apr 2019 11:59:19 +0200 Subject: [PATCH 19/35] Fix code style --- models/repo_branch.go | 12 ++++++------ modules/git/commit_info.go | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/models/repo_branch.go b/models/repo_branch.go index c61a06e663f0c..2f633f02b13ea 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -265,11 +265,11 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName func (branch *Branch) GetCommit() (*git.Commit, error) { if branch.gitRepo != nil { return branch.gitRepo.GetBranchCommit(branch.Name) - } else { - gitRepo, err := git.OpenRepository(branch.Path) - if err != nil { - return nil, err - } - return gitRepo.GetBranchCommit(branch.Name) } + + gitRepo, err := git.OpenRepository(branch.Path) + if err != nil { + return nil, err + } + return gitRepo.GetBranchCommit(branch.Name) } diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 05a0264ce78b0..cc1f521af165d 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -87,9 +87,8 @@ func getFullPath(treePath, path string) string { if treePath != "" { if path != "" { return treePath + "/" + path - } else { - return treePath } + return treePath } return path } From 43bfdaf531e0b29e9d977f6093858fe04132321e Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 1 Apr 2019 12:03:32 +0200 Subject: [PATCH 20/35] Fix spelling --- modules/git/commit_info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index cc1f521af165d..4a982928c6700 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -204,7 +204,7 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m default: // The file is present on more than one of the parent paths, so this is // a merge. We have to examine all the parent trees to find out where - // the change occured. pathChanged[i] would tell us that the file was + // the change occurred. pathChanged[i] would tell us that the file was // changed during the merge, but it wouldn't tell us the relevant commit // that introduced it. remainingPaths = append(remainingPaths, path) From 35b4419d761ef518d1de9d5d939092afcc50818d Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 1 Apr 2019 15:03:00 +0200 Subject: [PATCH 21/35] Address PR feedback Signed-off-by: Filip Navara --- models/repo_branch.go | 15 ++++++++------- modules/git/repo_tag.go | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/models/repo_branch.go b/models/repo_branch.go index 2f633f02b13ea..a647d8461d200 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -263,13 +263,14 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName // GetCommit returns all the commits of a branch func (branch *Branch) GetCommit() (*git.Commit, error) { - if branch.gitRepo != nil { - return branch.gitRepo.GetBranchCommit(branch.Name) - } + var err error - gitRepo, err := git.OpenRepository(branch.Path) - if err != nil { - return nil, err + if branch.gitRepo == nil { + branch.gitRepo, err = git.OpenRepository(branch.Path) + if err != nil { + return nil, err + } } - return gitRepo.GetBranchCommit(branch.Name) + + return branch.gitRepo.GetBranchCommit(branch.Name) } diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index fdeb7f9715baf..fd73cae2935f7 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -131,7 +131,7 @@ func (repo *Repository) GetTags() ([]string, error) { tags, err := repo.gogitRepo.Tags() if err != nil { - return nil, nil + return nil, err } tags.ForEach(func(tag *plumbing.Reference) error { From 29788d027cbaf90a2ba2fea647470052c917a038 Mon Sep 17 00:00:00 2001 From: Mura Li Date: Wed, 3 Apr 2019 22:53:49 +0800 Subject: [PATCH 22/35] Update vendor module list Signed-off-by: Filip Navara --- go.mod | 4 ++-- vendor/modules.txt | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 7ea9024730e4d..14f0ac6024044 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect github.com/elazarl/go-bindata-assetfs v0.0.0-20151224045452-57eb5e1fc594 // indirect - github.com/emirpasic/gods v1.12.0 // indirect + github.com/emirpasic/gods v1.12.0 github.com/etcd-io/bbolt v1.3.2 // indirect github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect @@ -127,7 +127,7 @@ require ( gopkg.in/ldap.v3 v3.0.2 gopkg.in/macaron.v1 v1.3.2 gopkg.in/redis.v2 v2.3.2 // indirect - gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect + gopkg.in/src-d/go-billy.v4 v4.3.0 gopkg.in/src-d/go-git.v4 v4.8.0 gopkg.in/testfixtures.v2 v2.5.0 mvdan.cc/xurls/v2 v2.0.0 diff --git a/vendor/modules.txt b/vendor/modules.txt index dae0d2ec1fa2d..03048dfe297e4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -409,8 +409,8 @@ gopkg.in/macaron.v1 # gopkg.in/redis.v2 v2.3.2 gopkg.in/redis.v2 # gopkg.in/src-d/go-billy.v4 v4.3.0 -gopkg.in/src-d/go-billy.v4 gopkg.in/src-d/go-billy.v4/osfs +gopkg.in/src-d/go-billy.v4 gopkg.in/src-d/go-billy.v4/util gopkg.in/src-d/go-billy.v4/helper/chroot gopkg.in/src-d/go-billy.v4/helper/polyfill @@ -418,13 +418,14 @@ gopkg.in/src-d/go-billy.v4/helper/polyfill gopkg.in/src-d/go-git.v4 gopkg.in/src-d/go-git.v4/config gopkg.in/src-d/go-git.v4/plumbing -gopkg.in/src-d/go-git.v4/internal/revision gopkg.in/src-d/go-git.v4/plumbing/cache gopkg.in/src-d/go-git.v4/plumbing/filemode +gopkg.in/src-d/go-git.v4/plumbing/object +gopkg.in/src-d/go-git.v4/storage/filesystem +gopkg.in/src-d/go-git.v4/internal/revision gopkg.in/src-d/go-git.v4/plumbing/format/gitignore gopkg.in/src-d/go-git.v4/plumbing/format/index gopkg.in/src-d/go-git.v4/plumbing/format/packfile -gopkg.in/src-d/go-git.v4/plumbing/object gopkg.in/src-d/go-git.v4/plumbing/protocol/packp gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/sideband @@ -433,7 +434,6 @@ gopkg.in/src-d/go-git.v4/plumbing/storer gopkg.in/src-d/go-git.v4/plumbing/transport gopkg.in/src-d/go-git.v4/plumbing/transport/client gopkg.in/src-d/go-git.v4/storage -gopkg.in/src-d/go-git.v4/storage/filesystem gopkg.in/src-d/go-git.v4/storage/memory gopkg.in/src-d/go-git.v4/utils/diff gopkg.in/src-d/go-git.v4/utils/ioutil @@ -442,16 +442,16 @@ gopkg.in/src-d/go-git.v4/utils/merkletrie/filesystem gopkg.in/src-d/go-git.v4/utils/merkletrie/index gopkg.in/src-d/go-git.v4/utils/merkletrie/noder gopkg.in/src-d/go-git.v4/plumbing/format/config +gopkg.in/src-d/go-git.v4/plumbing/format/diff gopkg.in/src-d/go-git.v4/utils/binary gopkg.in/src-d/go-git.v4/plumbing/format/idxfile -gopkg.in/src-d/go-git.v4/plumbing/format/diff +gopkg.in/src-d/go-git.v4/plumbing/format/objfile +gopkg.in/src-d/go-git.v4/storage/filesystem/dotgit gopkg.in/src-d/go-git.v4/plumbing/format/pktline gopkg.in/src-d/go-git.v4/plumbing/transport/file gopkg.in/src-d/go-git.v4/plumbing/transport/git gopkg.in/src-d/go-git.v4/plumbing/transport/http gopkg.in/src-d/go-git.v4/plumbing/transport/ssh -gopkg.in/src-d/go-git.v4/plumbing/format/objfile -gopkg.in/src-d/go-git.v4/storage/filesystem/dotgit gopkg.in/src-d/go-git.v4/utils/merkletrie/internal/frame gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common gopkg.in/src-d/go-git.v4/plumbing/transport/server From e28ede0cc7a12af8c74bd35c435cdb10a62fa882 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 5 Apr 2019 09:29:52 +0200 Subject: [PATCH 23/35] Fix getting trees by commit id Signed-off-by: Filip Navara --- modules/git/repo_tree.go | 11 +++++++++-- modules/git/tree.go | 23 +++++++++++++++++------ modules/git/tree_entry.go | 10 +++++----- routers/api/v1/repo/tree.go | 2 +- routers/api/v1/repo/tree_test.go | 5 +++-- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 4e1a337a5b085..97ae4c99c959b 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -4,10 +4,17 @@ package git -import "gopkg.in/src-d/go-git.v4/plumbing" +import ( + "gopkg.in/src-d/go-git.v4/plumbing" +) func (repo *Repository) getTree(id SHA1) (*Tree, error) { - gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id)) + commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) + if err != nil { + return nil, err + } + + gogitTree, err := commitObject.Tree() if err != nil { return nil, err } diff --git a/modules/git/tree.go b/modules/git/tree.go index 2f080840069eb..71f8ca35ac7a9 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -60,15 +60,28 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) { return g, nil } +func (t *Tree) loadTreeObject() error { + commitObject, err := t.repo.gogitRepo.CommitObject(plumbing.Hash(t.ID)) + if err != nil { + return err + } + + gogitTree, err := commitObject.Tree() + if err != nil { + return err + } + + t.gogitTree = gogitTree + return nil +} + // ListEntries returns all entries of current tree. func (t *Tree) ListEntries() (Entries, error) { if t.gogitTree == nil { - gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID)) + err := t.loadTreeObject() if err != nil { return nil, err } - - t.gogitTree = gogitTree } entries := make([]*TreeEntry, len(t.gogitTree.Entries)) @@ -86,12 +99,10 @@ func (t *Tree) ListEntries() (Entries, error) { // ListEntriesRecursive returns all entries of current tree recursively including all subtrees func (t *Tree) ListEntriesRecursive() (Entries, error) { if t.gogitTree == nil { - gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID)) + err := t.loadTreeObject() if err != nil { return nil, err } - - t.gogitTree = gogitTree } var entries []*TreeEntry diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index e2b24e74dbb92..7dffcbd0f70f1 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -21,15 +21,15 @@ type EntryMode int // one of these. const ( // EntryModeBlob - EntryModeBlob EntryMode = 0x0100644 + EntryModeBlob EntryMode = 0100644 // EntryModeExec - EntryModeExec EntryMode = 0x0100755 + EntryModeExec EntryMode = 0100755 // EntryModeSymlink - EntryModeSymlink EntryMode = 0x0120000 + EntryModeSymlink EntryMode = 0120000 // EntryModeCommit - EntryModeCommit EntryMode = 0x0160000 + EntryModeCommit EntryMode = 0160000 // EntryModeTree - EntryModeTree EntryMode = 0x0040000 + EntryModeTree EntryMode = 0040000 ) // TreeEntry the leaf in the git tree diff --git a/routers/api/v1/repo/tree.go b/routers/api/v1/repo/tree.go index 61984dcdb93d7..e262016aea4a5 100644 --- a/routers/api/v1/repo/tree.go +++ b/routers/api/v1/repo/tree.go @@ -130,7 +130,7 @@ func GetTreeBySHA(ctx *context.APIContext, sha string) *gitea.GitTreeResponse { i := e - rangeStart tree.Entries[e].Path = entries[e].Name() - tree.Entries[e].Mode = fmt.Sprintf("%06x", entries[e].Mode()) + tree.Entries[e].Mode = fmt.Sprintf("%06o", entries[e].Mode()) if entries[e].Mode() == git.EntryModeCommit { tree.Entries[e].Type = "commit" diff --git a/routers/api/v1/repo/tree_test.go b/routers/api/v1/repo/tree_test.go index 708516e97998b..efaca7a7b8fe2 100644 --- a/routers/api/v1/repo/tree_test.go +++ b/routers/api/v1/repo/tree_test.go @@ -5,9 +5,10 @@ package repo import ( - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/test" @@ -44,5 +45,5 @@ func TestGetTreeBySHA(t *testing.T) { TotalCount: 1, } - assert.EqualValues(t, tree, expectedTree) + assert.EqualValues(t, expectedTree, tree) } From 63a788e62cf128bbfb3b46e586914f70491599a6 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 5 Apr 2019 10:48:01 +0200 Subject: [PATCH 24/35] Fix remaining unit test failures --- modules/git/blob.go | 2 +- modules/git/blob_test.go | 17 +++++++++-------- modules/git/repo.go | 7 +++++++ modules/git/repo_commit.go | 16 ++++++++++++++++ modules/git/repo_tree.go | 13 ++++++------- modules/git/tree.go | 9 ++------- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/modules/git/blob.go b/modules/git/blob.go index 847450c01cfad..f80d4c09d9123 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -17,7 +17,7 @@ type Blob struct { // DataAsync gets a ReadCloser for the contents of a blob without reading it all. // Calling the Close function on the result will discard all unread output. func (b *Blob) DataAsync() (io.ReadCloser, error) { - gogitBlob, err := b.repo.gogitRepo.BlobObject(b.gogitTreeEntry.Hash) + gogitBlob, err := b.repo.gogitRepo.BlobObject(b.ID) if err != nil { return nil, err } diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index c91fd4cf4cadc..6304eb921c0f2 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -12,17 +12,9 @@ import ( "github.com/stretchr/testify/require" ) -var repoSelf = &Repository{ - Path: "./", -} - var testBlob = &Blob{ - repo: repoSelf, TreeEntry: &TreeEntry{ ID: MustIDFromString("a8d4b49dd073a4a38a7e58385eeff7cc52568697"), - ptree: &Tree{ - repo: repoSelf, - }, }, } @@ -48,6 +40,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ` + repo, err := OpenRepository("../../.git") + assert.NoError(t, err) + testBlob.repo = repo r, err := testBlob.DataAsync() assert.NoError(t, err) @@ -60,6 +55,12 @@ THE SOFTWARE. } func Benchmark_Blob_Data(b *testing.B) { + repo, err := OpenRepository("../../.git") + if err != nil { + b.Fatal(err) + } + testBlob.repo = repo + for i := 0; i < b.N; i++ { r, err := testBlob.DataAsync() if err != nil { diff --git a/modules/git/repo.go b/modules/git/repo.go index 252a84f3d18f4..fa17c484e9788 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -86,6 +86,13 @@ func OpenRepository(repoPath string) (*Repository, error) { } fs := osfs.New(repoPath) + _, err = fs.Stat(".git") + if err == nil { + fs, err = fs.Chroot(".git") + if err != nil { + return nil, err + } + } storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true}) gogitRepo, err := gogit.Open(storage, fs) if err != nil { diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 4a9e2111a8eba..f9bd7e9e14ff2 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -13,6 +13,7 @@ import ( "github.com/mcuadros/go-version" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // GetRefCommitID returns the last commit ID string of given reference (branch or tag). @@ -43,6 +44,8 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { } func (repo *Repository) getCommit(id SHA1) (*Commit, error) { + var tagObject *object.Tag + c, ok := repo.commitCache.Get(id.String()) if ok { log("Hit cache: %s", id) @@ -50,6 +53,12 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { } gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) + if err == plumbing.ErrObjectNotFound { + tagObject, err = repo.gogitRepo.TagObject(plumbing.Hash(id)) + if err == nil { + gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target) + } + } if err != nil { return nil, err } @@ -57,6 +66,13 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { commit := convertCommit(gogitCommit) commit.repo = repo + if tagObject != nil { + commit.CommitMessage = strings.TrimSpace(tagObject.Message) + commit.Author = &tagObject.Tagger + // FIXME: Add Payload + commit.Signature = &CommitGPGSignature{Signature: tagObject.PGPSignature} + } + tree, err := gogitCommit.Tree() if err != nil { return nil, err diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 97ae4c99c959b..2103c03264732 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -9,12 +9,7 @@ import ( ) func (repo *Repository) getTree(id SHA1) (*Tree, error) { - commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) - if err != nil { - return nil, err - } - - gogitTree, err := commitObject.Tree() + gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id)) if err != nil { return nil, err } @@ -39,5 +34,9 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { if err != nil { return nil, err } - return repo.getTree(id) + commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) + if err != nil { + return nil, err + } + return repo.getTree(SHA1(commitObject.TreeHash)) } diff --git a/modules/git/tree.go b/modules/git/tree.go index 71f8ca35ac7a9..a4ca28f593464 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -23,7 +23,7 @@ type Tree struct { ptree *Tree } -// NewTree create a new tree according the repository and commit id +// NewTree create a new tree according the repository and tree id func NewTree(repo *Repository, id SHA1) *Tree { return &Tree{ ID: id, @@ -61,12 +61,7 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) { } func (t *Tree) loadTreeObject() error { - commitObject, err := t.repo.gogitRepo.CommitObject(plumbing.Hash(t.ID)) - if err != nil { - return err - } - - gogitTree, err := commitObject.Tree() + gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID)) if err != nil { return err } From 1a0a1ae6b32e17ee6f2289e656a4e73d6513fd38 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 5 Apr 2019 11:22:46 +0200 Subject: [PATCH 25/35] Fix GetTreeBySHA --- modules/git/repo_tree.go | 7 ++++++- modules/git/tree.go | 5 +++-- routers/api/v1/repo/tree.go | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 2103c03264732..bc990a3ec09a1 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -38,5 +38,10 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { if err != nil { return nil, err } - return repo.getTree(SHA1(commitObject.TreeHash)) + treeObject, err := repo.getTree(SHA1(commitObject.TreeHash)) + if err != nil { + return nil, err + } + treeObject.CommitID = id + return treeObject, nil } diff --git a/modules/git/tree.go b/modules/git/tree.go index a4ca28f593464..366994a773bff 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -14,8 +14,9 @@ import ( // Tree represents a flat directory listing. type Tree struct { - ID SHA1 - repo *Repository + ID SHA1 + CommitID SHA1 + repo *Repository gogitTree *object.Tree diff --git a/routers/api/v1/repo/tree.go b/routers/api/v1/repo/tree.go index e262016aea4a5..d0bfadebd2a06 100644 --- a/routers/api/v1/repo/tree.go +++ b/routers/api/v1/repo/tree.go @@ -76,7 +76,7 @@ func GetTreeBySHA(ctx *context.APIContext, sha string) *gitea.GitTreeResponse { } tree := new(gitea.GitTreeResponse) repoID := strings.TrimRight(setting.AppURL, "/") + "/api/v1/repos/" + ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name - tree.SHA = gitTree.ID.String() + tree.SHA = gitTree.CommitID.String() tree.URL = repoID + "/git/trees/" + tree.SHA var entries git.Entries if ctx.QueryBool("recursive") { From 2801e134eb0e14270f1816aee3e5e20674b91b9a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sun, 14 Apr 2019 18:55:14 +0200 Subject: [PATCH 26/35] Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara --- modules/git/commit.go | 11 +++++++++++ modules/git/repo.go | 4 +--- modules/git/repo_commit.go | 15 --------------- routers/repo/commit.go | 1 + templates/repo/diff/page.tmpl | 2 +- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/modules/git/commit.go b/modules/git/commit.go index b0a83d08e2dc6..e3f69e2b28469 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -380,6 +380,17 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) { return nil, nil } +// GetBranchName gets the closes branch name (as returned by 'git name-rev') +func (c *Commit) GetBranchName() (string, error) { + data, err := NewCommand("name-rev", c.ID.String()).RunInDirBytes(c.repo.Path) + if err != nil { + return "", err + } + + // name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12" + return strings.Split(strings.Split(string(data), " ")[1], "~")[0], nil +} + // CommitFileStatus represents status of files in a commit. type CommitFileStatus struct { Added []string diff --git a/modules/git/repo.go b/modules/git/repo.go index fa17c484e9788..af57b05a062a9 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -27,8 +27,7 @@ import ( type Repository struct { Path string - commitCache *ObjectCache - tagCache *ObjectCache + tagCache *ObjectCache gogitRepo *gogit.Repository gogitStorage *filesystem.Storage @@ -103,7 +102,6 @@ func OpenRepository(repoPath string) (*Repository, error) { Path: repoPath, gogitRepo: gogitRepo, gogitStorage: storage, - commitCache: newObjectCache(), tagCache: newObjectCache(), }, nil } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index f9bd7e9e14ff2..39bcb94a00d37 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -46,12 +46,6 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { func (repo *Repository) getCommit(id SHA1) (*Commit, error) { var tagObject *object.Tag - c, ok := repo.commitCache.Get(id.String()) - if ok { - log("Hit cache: %s", id) - return c.(*Commit), nil - } - gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) if err == plumbing.ErrObjectNotFound { tagObject, err = repo.gogitRepo.TagObject(plumbing.Hash(id)) @@ -81,15 +75,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { commit.Tree.ID = tree.Hash commit.Tree.gogitTree = tree - data, err := NewCommand("name-rev", id.String()).RunInDirBytes(repo.Path) - if err != nil { - return nil, err - } - - // name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12" - commit.Branch = strings.Split(strings.Split(string(data), " ")[1], "~")[0] - repo.commitCache.Set(id.String(), commit) - return commit, nil } diff --git a/routers/repo/commit.go b/routers/repo/commit.go index a1e9b8bc5d1fd..9263bcac7fb81 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -243,6 +243,7 @@ func Diff(ctx *context.Context) { ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0]) } ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID) + ctx.Data["BranchName"], err = commit.GetBranchName() ctx.HTML(200, tplDiff) } diff --git a/templates/repo/diff/page.tmpl b/templates/repo/diff/page.tmpl index 3f383add948b1..c8f5a3d9f0efc 100644 --- a/templates/repo/diff/page.tmpl +++ b/templates/repo/diff/page.tmpl @@ -13,7 +13,7 @@ {{if IsMultilineCommitMessage .Commit.Message}}
{{RenderCommitBody .Commit.Message $.RepoLink $.Repository.ComposeMetas}}
{{end}} - {{.Commit.Branch}} + {{.BranchName}}
From cf4ca8422d831009a932b6ebac3cb982caf5003c Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sun, 14 Apr 2019 19:16:35 +0200 Subject: [PATCH 27/35] Move Branch code to git module --- models/error.go | 15 ------ models/pull.go | 4 +- models/repo_branch.go | 64 ++++-------------------- modules/git/error.go | 15 ++++++ modules/git/repo_branch.go | 48 +++++++++++++++++- routers/api/v1/convert/convert.go | 2 +- routers/api/v1/repo/branch.go | 4 +- routers/repo/setting_protected_branch.go | 4 +- 8 files changed, 77 insertions(+), 79 deletions(-) diff --git a/models/error.go b/models/error.go index f079af4e1f65e..4e1fb1d1a7533 100644 --- a/models/error.go +++ b/models/error.go @@ -803,21 +803,6 @@ func (err ErrUserDoesNotHaveAccessToRepo) Error() string { // |______ / |__| (____ /___| /\___ >___| / // \/ \/ \/ \/ \/ -// ErrBranchNotExist represents a "BranchNotExist" kind of error. -type ErrBranchNotExist struct { - Name string -} - -// IsErrBranchNotExist checks if an error is a ErrBranchNotExist. -func IsErrBranchNotExist(err error) bool { - _, ok := err.(ErrBranchNotExist) - return ok -} - -func (err ErrBranchNotExist) Error() string { - return fmt.Sprintf("branch does not exist [name: %s]", err.Name) -} - // ErrBranchAlreadyExists represents an error that branch with such name already exists type ErrBranchAlreadyExists struct { BranchName string diff --git a/models/pull.go b/models/pull.go index 88f9b1f6e8a28..0fee8cd840d1a 100644 --- a/models/pull.go +++ b/models/pull.go @@ -165,8 +165,8 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest { var ( - baseBranch *Branch - headBranch *Branch + baseBranch *git.Branch + headBranch *git.Branch baseCommit *git.Commit headCommit *git.Commit err error diff --git a/models/repo_branch.go b/models/repo_branch.go index a647d8461d200..b87d5f70a4cfe 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -86,56 +86,24 @@ func (repo *Repository) DeleteLocalBranch(branchName string) error { return deleteLocalBranch(repo.LocalCopyPath(), repo.DefaultBranch, branchName) } -// Branch holds the branch information -type Branch struct { - Path string - Name string - - gitRepo *git.Repository -} - -// GetBranchesByPath returns a branch by it's path -func GetBranchesByPath(path string) ([]*Branch, error) { - gitRepo, err := git.OpenRepository(path) - if err != nil { - return nil, err - } - - brs, err := gitRepo.GetBranches() - if err != nil { - return nil, err - } - - branches := make([]*Branch, len(brs)) - for i := range brs { - branches[i] = &Branch{ - Path: path, - Name: brs[i], - gitRepo: gitRepo, - } - } - return branches, nil -} - // CanCreateBranch returns true if repository meets the requirements for creating new branches. func (repo *Repository) CanCreateBranch() bool { return !repo.IsMirror } -// GetBranch returns a branch by it's name -func (repo *Repository) GetBranch(branch string) (*Branch, error) { - if !git.IsBranchExist(repo.RepoPath(), branch) { - return nil, ErrBranchNotExist{branch} +// GetBranch returns a branch by its name +func (repo *Repository) GetBranch(branch string) (*git.Branch, error) { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + return nil, err } - return &Branch{ - Path: repo.RepoPath(), - Name: branch, - }, nil + + return gitRepo.GetBranch(branch) } // GetBranches returns all the branches of a repository -func (repo *Repository) GetBranches() ([]*Branch, error) { - return GetBranchesByPath(repo.RepoPath()) +func (repo *Repository) GetBranches() ([]*git.Branch, error) { + return git.GetBranchesByPath(repo.RepoPath()) } // CheckBranchName validates branch name with existing repository branches @@ -260,17 +228,3 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName return nil } - -// GetCommit returns all the commits of a branch -func (branch *Branch) GetCommit() (*git.Commit, error) { - var err error - - if branch.gitRepo == nil { - branch.gitRepo, err = git.OpenRepository(branch.Path) - if err != nil { - return nil, err - } - } - - return branch.gitRepo.GetBranchCommit(branch.Name) -} diff --git a/modules/git/error.go b/modules/git/error.go index 1aae5a37a2891..6e4f26de13502 100644 --- a/modules/git/error.go +++ b/modules/git/error.go @@ -64,3 +64,18 @@ func IsErrUnsupportedVersion(err error) bool { func (err ErrUnsupportedVersion) Error() string { return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required) } + +// ErrBranchNotExist represents a "BranchNotExist" kind of error. +type ErrBranchNotExist struct { + Name string +} + +// IsErrBranchNotExist checks if an error is a ErrBranchNotExist. +func IsErrBranchNotExist(err error) bool { + _, ok := err.(ErrBranchNotExist) + return ok +} + +func (err ErrBranchNotExist) Error() string { + return fmt.Sprintf("branch does not exist [name: %s]", err.Name) +} diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index bce80c1b0882f..83689ee9dc03d 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -39,6 +39,8 @@ func (repo *Repository) IsBranchExist(name string) bool { type Branch struct { Name string Path string + + gitRepo *Repository } // GetHEADBranch returns corresponding branch of HEAD. @@ -54,8 +56,9 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) { } return &Branch{ - Name: stdout[len(BranchPrefix):], - Path: stdout, + Name: stdout[len(BranchPrefix):], + Path: stdout, + gitRepo: repo, }, nil } @@ -84,6 +87,42 @@ func (repo *Repository) GetBranches() ([]string, error) { return branchNames, nil } +// GetBranch returns a branch by it's name +func (repo *Repository) GetBranch(branch string) (*Branch, error) { + if !repo.IsBranchExist(branch) { + return nil, ErrBranchNotExist{branch} + } + return &Branch{ + Path: repo.Path, + Name: branch, + gitRepo: repo, + }, nil +} + +// GetBranchesByPath returns a branch by it's path +func GetBranchesByPath(path string) ([]*Branch, error) { + gitRepo, err := OpenRepository(path) + if err != nil { + return nil, err + } + + brs, err := gitRepo.GetBranches() + if err != nil { + return nil, err + } + + branches := make([]*Branch, len(brs)) + for i := range brs { + branches[i] = &Branch{ + Path: path, + Name: brs[i], + gitRepo: gitRepo, + } + } + + return branches, nil +} + // DeleteBranchOptions Option(s) for delete branch type DeleteBranchOptions struct { Force bool @@ -132,3 +171,8 @@ func (repo *Repository) RemoveRemote(name string) error { _, err := NewCommand("remote", "remove", name).RunInDir(repo.Path) return err } + +// GetCommit returns the head commit of a branch +func (branch *Branch) GetCommit() (*Commit, error) { + return branch.gitRepo.GetBranchCommit(branch.Name) +} diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go index 14344e581c958..0cedf2af19588 100644 --- a/routers/api/v1/convert/convert.go +++ b/routers/api/v1/convert/convert.go @@ -26,7 +26,7 @@ func ToEmail(email *models.EmailAddress) *api.Email { } // ToBranch convert a commit and branch to an api.Branch -func ToBranch(repo *models.Repository, b *models.Branch, c *git.Commit) *api.Branch { +func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch { return &api.Branch{ Name: b.Name, Commit: ToCommit(repo, c), diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index e20eef6139c78..481201dd188b3 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -5,8 +5,8 @@ package repo import ( - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/routers/api/v1/convert" api "code.gitea.io/sdk/gitea" @@ -47,7 +47,7 @@ func GetBranch(ctx *context.APIContext) { } branch, err := ctx.Repo.Repository.GetBranch(ctx.Repo.BranchName) if err != nil { - if models.IsErrBranchNotExist(err) { + if git.IsErrBranchNotExist(err) { ctx.NotFound(err) } else { ctx.Error(500, "GetBranch", err) diff --git a/routers/repo/setting_protected_branch.go b/routers/repo/setting_protected_branch.go index 05ad0ec0ec9a5..b5a115b6a464d 100644 --- a/routers/repo/setting_protected_branch.go +++ b/routers/repo/setting_protected_branch.go @@ -103,7 +103,7 @@ func SettingsProtectedBranch(c *context.Context) { protectBranch, err := models.GetProtectedBranchBy(c.Repo.Repository.ID, branch) if err != nil { - if !models.IsErrBranchNotExist(err) { + if !git.IsErrBranchNotExist(err) { c.ServerError("GetProtectBranchOfRepoByName", err) return } @@ -152,7 +152,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch) if err != nil { - if !models.IsErrBranchNotExist(err) { + if !git.IsErrBranchNotExist(err) { ctx.ServerError("GetProtectBranchOfRepoByName", err) return } From c9ff5c5e4cb8d6cdb6cdf8d2b3e3650fc86bfadb Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sun, 14 Apr 2019 21:30:13 +0200 Subject: [PATCH 28/35] Clean up GPG signature verification and fix it for tagged commits --- modules/git/commit.go | 16 ---------------- modules/git/repo_commit.go | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/modules/git/commit.go b/modules/git/commit.go index e3f69e2b28469..a56b866646221 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -38,22 +38,6 @@ type CommitGPGSignature struct { Payload string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data } -// similar to https://github.com/git/git/blob/3bc53220cb2dcf709f7a027a3f526befd021d858/commit.c#L1128 -func newGPGSignatureFromCommitline(data []byte, signatureStart int, tag bool) (*CommitGPGSignature, error) { - sig := new(CommitGPGSignature) - signatureEnd := bytes.LastIndex(data, []byte("-----END PGP SIGNATURE-----")) - if signatureEnd == -1 { - return nil, fmt.Errorf("end of commit signature not found") - } - sig.Signature = strings.Replace(string(data[signatureStart:signatureEnd+27]), "\n ", "\n", -1) - if tag { - sig.Payload = string(data[:signatureStart-1]) - } else { - sig.Payload = string(data[:signatureStart-8]) + string(data[signatureEnd+27:]) - } - return sig, nil -} - func convertPGPSignature(c *object.Commit) *CommitGPGSignature { if c.PGPSignature == "" { return nil diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 39bcb94a00d37..3a3e219a72735 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -7,6 +7,7 @@ package git import ( "bytes" "container/list" + "fmt" "strconv" "strings" @@ -43,6 +44,38 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { return strings.TrimSpace(stdout), nil } +func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature { + if t.PGPSignature == "" { + return nil + } + + var w strings.Builder + var err error + + if _, err = fmt.Fprintf(&w, + "object %s\ntype %s\ntag %s\ntagger ", + t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil { + return nil + } + + if err = t.Tagger.Encode(&w); err != nil { + return nil + } + + if _, err = fmt.Fprintf(&w, "\n\n"); err != nil { + return nil + } + + if _, err = fmt.Fprintf(&w, t.Message); err != nil { + return nil + } + + return &CommitGPGSignature{ + Signature: t.PGPSignature, + Payload: strings.TrimSpace(w.String()) + "\n", + } +} + func (repo *Repository) getCommit(id SHA1) (*Commit, error) { var tagObject *object.Tag @@ -63,8 +96,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { if tagObject != nil { commit.CommitMessage = strings.TrimSpace(tagObject.Message) commit.Author = &tagObject.Tagger - // FIXME: Add Payload - commit.Signature = &CommitGPGSignature{Signature: tagObject.PGPSignature} + commit.Signature = convertPGPSignatureForTag(tagObject) } tree, err := gogitCommit.Tree() From b2ab51161661812b262e155895eac76eba07bd67 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 17 Apr 2019 08:33:33 +0200 Subject: [PATCH 29/35] Address PR feedback (import formatting, copyright headers) --- models/pull.go | 1 + models/repo_branch.go | 1 + modules/git/blob.go | 1 + modules/git/blob_test.go | 1 + modules/git/repo.go | 1 - modules/git/repo_commit.go | 2 +- modules/git/repo_tag.go | 1 + modules/git/repo_tree.go | 1 + modules/git/sha1.go | 1 + modules/git/signature.go | 1 + modules/git/tree.go | 1 + modules/git/tree_blob.go | 2 +- modules/git/tree_entry.go | 2 +- routers/api/v1/repo/branch.go | 1 + routers/api/v1/repo/tree_test.go | 4 ++-- 15 files changed, 15 insertions(+), 6 deletions(-) diff --git a/models/pull.go b/models/pull.go index 0fee8cd840d1a..d059081a44ae0 100644 --- a/models/pull.go +++ b/models/pull.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/models/repo_branch.go b/models/repo_branch.go index b87d5f70a4cfe..0958e23974531 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -1,4 +1,5 @@ // Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/blob.go b/modules/git/blob.go index f80d4c09d9123..0d4ed5a19b994 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 6304eb921c0f2..33268e554607c 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/repo.go b/modules/git/repo.go index af57b05a062a9..f86c4aae5c9a5 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -16,7 +16,6 @@ import ( "time" "github.com/Unknwon/com" - "gopkg.in/src-d/go-billy.v4/osfs" gogit "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing/cache" diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 3a3e219a72735..d22b088d989b0 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. @@ -12,7 +13,6 @@ import ( "strings" "github.com/mcuadros/go-version" - "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" ) diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index fd73cae2935f7..8c72528933ccd 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index bc990a3ec09a1..8a024fe6ac6e1 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/sha1.go b/modules/git/sha1.go index bee4801437387..57b06fe738ab8 100644 --- a/modules/git/sha1.go +++ b/modules/git/sha1.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/signature.go b/modules/git/signature.go index 69b096ed65c04..3f67bceb09678 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/tree.go b/modules/git/tree.go index 366994a773bff..8f55d7a8c5d3f 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index 4a11e4aea096e..14237df6e84ab 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. @@ -10,7 +11,6 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" - "gopkg.in/src-d/go-git.v4/plumbing/object" ) diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 7dffcbd0f70f1..6beb4dafe0112 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 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. @@ -10,7 +11,6 @@ import ( "strings" "gopkg.in/src-d/go-git.v4/plumbing/filemode" - "gopkg.in/src-d/go-git.v4/plumbing/object" ) diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 481201dd188b3..b9a23d3b51b50 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -1,4 +1,5 @@ // Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 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. diff --git a/routers/api/v1/repo/tree_test.go b/routers/api/v1/repo/tree_test.go index efaca7a7b8fe2..693e9c5f01f20 100644 --- a/routers/api/v1/repo/tree_test.go +++ b/routers/api/v1/repo/tree_test.go @@ -7,12 +7,12 @@ package repo import ( "testing" - "github.com/stretchr/testify/assert" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/test" "code.gitea.io/sdk/gitea" + + "github.com/stretchr/testify/assert" ) func TestGetTreeBySHA(t *testing.T) { From 5ddbcd790266b6396be5841326493b8a858ccf3e Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 17 Apr 2019 20:17:24 +0200 Subject: [PATCH 30/35] Make blob lookup by SHA working --- modules/git/blob.go | 24 +++++++++++++++++------- modules/git/repo_blob.go | 17 +++++++++-------- modules/git/tree_entry.go | 11 +++++++++-- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/modules/git/blob.go b/modules/git/blob.go index f402ca8749ba0..ecd08b5d6c884 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -9,23 +9,33 @@ import ( "encoding/base64" "io" "io/ioutil" + + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // Blob represents a Git object. type Blob struct { - repo *Repository - *TreeEntry + ID SHA1 + + repo *Repository + gogitBlob *object.Blob + name string } // DataAsync gets a ReadCloser for the contents of a blob without reading it all. // Calling the Close function on the result will discard all unread output. func (b *Blob) DataAsync() (io.ReadCloser, error) { - gogitBlob, err := b.repo.gogitRepo.BlobObject(b.ID) - if err != nil { - return nil, err - } + return b.gogitBlob.Reader() +} + +// Size returns the uncompressed size of the blob +func (b *Blob) Size() int64 { + return b.gogitBlob.Size +} - return gogitBlob.Reader() +// Name returns name of the tree entry this blob object was created from (or empty string) +func (b *Blob) Name() string { + return b.name } // GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string diff --git a/modules/git/repo_blob.go b/modules/git/repo_blob.go index a9445a1f7ab7c..0ed1b60aeb087 100644 --- a/modules/git/repo_blob.go +++ b/modules/git/repo_blob.go @@ -4,19 +4,20 @@ package git +import ( + "gopkg.in/src-d/go-git.v4/plumbing" +) + func (repo *Repository) getBlob(id SHA1) (*Blob, error) { - if _, err := NewCommand("cat-file", "-p", id.String()).RunInDir(repo.Path); err != nil { + gogitBlob, err := repo.gogitRepo.BlobObject(plumbing.Hash(id)) + if err != nil { return nil, ErrNotExist{id.String(), ""} } return &Blob{ - repo: repo, - TreeEntry: &TreeEntry{ - ID: id, - ptree: &Tree{ - repo: repo, - }, - }, + ID: id, + repo: repo, + gogitBlob: gogitBlob, }, nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 67149a6b8fa4c..61c47d5b2ef6f 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -103,11 +103,18 @@ func (te *TreeEntry) IsRegular() bool { return te.gogitTreeEntry.Mode == filemode.Regular } -// Blob retrun the blob object the entry +// Blob returns the blob object the entry func (te *TreeEntry) Blob() *Blob { + gogitBlob, err := te.ptree.repo.gogitRepo.BlobObject(te.gogitTreeEntry.Hash) + if err != nil { + return nil + } + return &Blob{ + ID: te.gogitTreeEntry.Hash, repo: te.ptree.repo, - TreeEntry: te, + gogitBlob: gogitBlob, + name: te.Name(), } } From beef9e0dbac06b459b79a222138986af755c1677 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 17 Apr 2019 20:25:13 +0200 Subject: [PATCH 31/35] Update tests to use public API --- modules/git/blob.go | 1 - modules/git/blob_test.go | 14 ++++++-------- modules/git/repo_blob.go | 1 - modules/git/tree_entry.go | 1 - 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/git/blob.go b/modules/git/blob.go index ecd08b5d6c884..7c7523a019ed0 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -17,7 +17,6 @@ import ( type Blob struct { ID SHA1 - repo *Repository gogitBlob *object.Blob name string } diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 33268e554607c..66c046ecc8d08 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -13,12 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -var testBlob = &Blob{ - TreeEntry: &TreeEntry{ - ID: MustIDFromString("a8d4b49dd073a4a38a7e58385eeff7cc52568697"), - }, -} - func TestBlob_Data(t *testing.T) { output := `Copyright (c) 2016 The Gitea Authors Copyright (c) 2015 The Gogs Authors @@ -43,7 +37,8 @@ THE SOFTWARE. ` repo, err := OpenRepository("../../.git") assert.NoError(t, err) - testBlob.repo = repo + testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") + assert.NoError(t, err) r, err := testBlob.DataAsync() assert.NoError(t, err) @@ -60,7 +55,10 @@ func Benchmark_Blob_Data(b *testing.B) { if err != nil { b.Fatal(err) } - testBlob.repo = repo + testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") + if err != nil { + b.Fatal(err) + } for i := 0; i < b.N; i++ { r, err := testBlob.DataAsync() diff --git a/modules/git/repo_blob.go b/modules/git/repo_blob.go index 0ed1b60aeb087..2301a772e1b79 100644 --- a/modules/git/repo_blob.go +++ b/modules/git/repo_blob.go @@ -16,7 +16,6 @@ func (repo *Repository) getBlob(id SHA1) (*Blob, error) { return &Blob{ ID: id, - repo: repo, gogitBlob: gogitBlob, }, nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 61c47d5b2ef6f..50474631ad854 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -112,7 +112,6 @@ func (te *TreeEntry) Blob() *Blob { return &Blob{ ID: te.gogitTreeEntry.Hash, - repo: te.ptree.repo, gogitBlob: gogitBlob, name: te.Name(), } From b11cf53527a642335fe9683212547b4c59b4b713 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 17 Apr 2019 20:51:10 +0200 Subject: [PATCH 32/35] Allow getting content from any type of object through the blob interface --- modules/git/blob.go | 10 +++++----- modules/git/repo_blob.go | 6 +++--- modules/git/tree_entry.go | 9 +++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/modules/git/blob.go b/modules/git/blob.go index 7c7523a019ed0..171b4a1010d85 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -10,26 +10,26 @@ import ( "io" "io/ioutil" - "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing" ) // Blob represents a Git object. type Blob struct { ID SHA1 - gogitBlob *object.Blob - name string + gogitEncodedObj plumbing.EncodedObject + name string } // DataAsync gets a ReadCloser for the contents of a blob without reading it all. // Calling the Close function on the result will discard all unread output. func (b *Blob) DataAsync() (io.ReadCloser, error) { - return b.gogitBlob.Reader() + return b.gogitEncodedObj.Reader() } // Size returns the uncompressed size of the blob func (b *Blob) Size() int64 { - return b.gogitBlob.Size + return b.gogitEncodedObj.Size() } // Name returns name of the tree entry this blob object was created from (or empty string) diff --git a/modules/git/repo_blob.go b/modules/git/repo_blob.go index 2301a772e1b79..db63491ce47eb 100644 --- a/modules/git/repo_blob.go +++ b/modules/git/repo_blob.go @@ -9,14 +9,14 @@ import ( ) func (repo *Repository) getBlob(id SHA1) (*Blob, error) { - gogitBlob, err := repo.gogitRepo.BlobObject(plumbing.Hash(id)) + encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id)) if err != nil { return nil, ErrNotExist{id.String(), ""} } return &Blob{ - ID: id, - gogitBlob: gogitBlob, + ID: id, + gogitEncodedObj: encodedObj, }, nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 50474631ad854..fe2fd14f971e8 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -10,6 +10,7 @@ import ( "sort" "strings" + "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" "gopkg.in/src-d/go-git.v4/plumbing/object" ) @@ -105,15 +106,15 @@ func (te *TreeEntry) IsRegular() bool { // Blob returns the blob object the entry func (te *TreeEntry) Blob() *Blob { - gogitBlob, err := te.ptree.repo.gogitRepo.BlobObject(te.gogitTreeEntry.Hash) + encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash) if err != nil { return nil } return &Blob{ - ID: te.gogitTreeEntry.Hash, - gogitBlob: gogitBlob, - name: te.Name(), + ID: te.gogitTreeEntry.Hash, + gogitEncodedObj: encodedObj, + name: te.Name(), } } From 0ee4399d19c0b1d06a82c4801f33dfabd9bb67f7 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 17 Apr 2019 20:52:36 +0200 Subject: [PATCH 33/35] Change test to actually expect the object content that is in the GIT repository --- modules/repofiles/blob_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/repofiles/blob_test.go b/modules/repofiles/blob_test.go index 260b775fc4b02..55320345f9ea0 100644 --- a/modules/repofiles/blob_test.go +++ b/modules/repofiles/blob_test.go @@ -27,7 +27,7 @@ func TestGetBlobBySHA(t *testing.T) { gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Params(":sha")) expectedGBR := &api.GitBlobResponse{ - Content: "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo=", + Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK", Encoding: "base64", URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d", SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", From e3c3123903f81b94528182fbe7097dd10a6a355f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 17 Apr 2019 21:00:49 +0200 Subject: [PATCH 34/35] Change one more test to actually expect the object content that is in the GIT repository --- integrations/api_repo_git_blobs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/api_repo_git_blobs_test.go b/integrations/api_repo_git_blobs_test.go index 560f108fca09e..69bb6cde23cd3 100644 --- a/integrations/api_repo_git_blobs_test.go +++ b/integrations/api_repo_git_blobs_test.go @@ -38,7 +38,7 @@ func TestAPIReposGitBlobs(t *testing.T) { var gitBlobResponse api.GitBlobResponse DecodeJSON(t, resp, &gitBlobResponse) assert.NotNil(t, gitBlobResponse) - expectedContent := "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo=" + expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK" assert.Equal(t, expectedContent, gitBlobResponse.Content) // Tests a private repo with no token so will fail From c036283236f4f0a2d7baac6aa76402617bc41e25 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 19 Apr 2019 11:56:49 +0200 Subject: [PATCH 35/35] Add comments --- modules/git/commit_info.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 4a982928c6700..02c6f710ad454 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -13,6 +13,7 @@ import ( // GetCommitsInfo gets information of all commits that are corresponding to these entries func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) { entryPaths := make([]string, len(tes)+1) + // Get the commit for the treePath itself entryPaths[0] = "" for i, entry := range tes { entryPaths[i+1] = entry.Name() @@ -51,6 +52,9 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom } } + // Retrieve the commit for the treePath itself (see above). We basically + // get it for free during the tree traversal and it's used for listing + // pages to display information about newest commit for a given path. var treeCommit *Commit if rev, ok := revs[""]; ok { treeCommit = convertCommit(rev)