|
- // Copyright 2015 The Gogs Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
-
- package git
-
- import (
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- )
-
- // EntryMode the type of the object in the git tree
- type EntryMode int
-
- // There are only a few file modes in Git. They look like unix file modes, but they can only be
- // one of these.
- const (
- // EntryModeBlob
- EntryModeBlob EntryMode = 0100644
- // EntryModeExec
- EntryModeExec EntryMode = 0100755
- // EntryModeSymlink
- EntryModeSymlink EntryMode = 0120000
- // EntryModeCommit
- EntryModeCommit EntryMode = 0160000
- // EntryModeTree
- EntryModeTree EntryMode = 0040000
- )
-
- // TreeEntry the leaf in the git tree
- type TreeEntry struct {
- ID SHA1
- Type ObjectType
-
- mode EntryMode
- name string
-
- ptree *Tree
-
- commited bool
-
- size int64
- sized bool
- }
-
- // Name returns the name of the entry
- func (te *TreeEntry) Name() string {
- return te.name
- }
-
- // Size returns the size of the entry
- func (te *TreeEntry) Size() int64 {
- if te.IsDir() {
- return 0
- } else if te.sized {
- return te.size
- }
-
- stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path)
- if err != nil {
- return 0
- }
-
- te.sized = true
- te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
- return te.size
- }
-
- // IsSubModule if the entry is a sub module
- func (te *TreeEntry) IsSubModule() bool {
- return te.mode == EntryModeCommit
- }
-
- // IsDir if the entry is a sub dir
- func (te *TreeEntry) IsDir() bool {
- return te.mode == EntryModeTree
- }
-
- // IsLink if the entry is a symlink
- func (te *TreeEntry) IsLink() bool {
- return te.mode == EntryModeSymlink
- }
-
- // Blob retrun the blob object the entry
- func (te *TreeEntry) Blob() *Blob {
- return &Blob{
- repo: te.ptree.repo,
- TreeEntry: te,
- }
- }
-
- // GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
- func (te *TreeEntry) GetSubJumpablePathName() string {
- if te.IsSubModule() || !te.IsDir() {
- return ""
- }
- tree, err := te.ptree.SubTree(te.name)
- if err != nil {
- 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
- }
-
- // Entries a list of entry
- type Entries []*TreeEntry
-
- var sorter = []func(t1, t2 *TreeEntry) bool{
- func(t1, t2 *TreeEntry) bool {
- return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
- },
- func(t1, t2 *TreeEntry) bool {
- return t1.name < t2.name
- },
- }
-
- func (tes Entries) Len() int { return len(tes) }
- func (tes Entries) Swap(i, j int) { tes[i], tes[j] = tes[j], tes[i] }
- func (tes Entries) Less(i, j int) bool {
- t1, t2 := tes[i], tes[j]
- var k int
- for k = 0; k < len(sorter)-1; k++ {
- s := sorter[k]
- switch {
- case s(t1, t2):
- return true
- case s(t2, t1):
- return false
- }
- }
- return sorter[k](t1, t2)
- }
-
- // Sort sort the list of entry
- func (tes Entries) Sort() {
- sort.Sort(tes)
- }
-
- // getCommitInfoState transient state for getting commit info for entries
- type getCommitInfoState struct {
- entries map[string]*TreeEntry // map from filepath to entry
- commits map[string]*Commit // map from entry name to commit
- lastCommitHash string
- lastCommit *Commit
- treePath string
- headCommit *Commit
- nextSearchSize int // next number of commits to search for
- }
-
- func initGetCommitInfoState(entries Entries, headCommit *Commit, treePath string) *getCommitInfoState {
- entriesByPath := make(map[string]*TreeEntry, len(entries))
- for _, entry := range entries {
- entriesByPath[filepath.Join(treePath, entry.Name())] = entry
- }
- return &getCommitInfoState{
- entries: entriesByPath,
- commits: make(map[string]*Commit, len(entriesByPath)),
- treePath: treePath,
- headCommit: headCommit,
- nextSearchSize: 16,
- }
- }
-
- // GetCommitsInfo gets information of all commits that are corresponding to these entries
- func (tes Entries) GetCommitsInfo(commit *Commit, treePath string) ([][]interface{}, error) {
- state := initGetCommitInfoState(tes, commit, treePath)
- if err := getCommitsInfo(state); err != nil {
- return nil, err
- }
-
- commitsInfo := make([][]interface{}, len(tes))
- for i, entry := range tes {
- commit = state.commits[filepath.Join(treePath, entry.Name())]
- switch entry.Type {
- case ObjectCommit:
- subModuleURL := ""
- if subModule, err := state.headCommit.GetSubModule(entry.Name()); err != nil {
- return nil, err
- } else if subModule != nil {
- subModuleURL = subModule.URL
- }
- subModuleFile := NewSubModuleFile(commit, subModuleURL, entry.ID.String())
- commitsInfo[i] = []interface{}{entry, subModuleFile}
- default:
- commitsInfo[i] = []interface{}{entry, commit}
- }
- }
- return commitsInfo, nil
- }
-
- func (state *getCommitInfoState) nextCommit(hash string) {
- state.lastCommitHash = hash
- state.lastCommit = nil
- }
-
- func (state *getCommitInfoState) commit() (*Commit, error) {
- var err error
- if state.lastCommit == nil {
- state.lastCommit, err = state.headCommit.repo.GetCommit(state.lastCommitHash)
- }
- return state.lastCommit, err
- }
-
- func (state *getCommitInfoState) update(path string) error {
- relPath, err := filepath.Rel(state.treePath, path)
- if err != nil {
- return nil
- }
- var entryPath string
- if index := strings.IndexRune(relPath, '/'); index >= 0 {
- entryPath = filepath.Join(state.treePath, relPath[:index])
- } else {
- entryPath = path
- }
- if _, ok := state.entries[entryPath]; !ok {
- return nil
- } else if _, ok := state.commits[entryPath]; ok {
- return nil
- }
- state.commits[entryPath], err = state.commit()
- return err
- }
-
- func getCommitsInfo(state *getCommitInfoState) error {
- for len(state.entries) > len(state.commits) {
- if err := getNextCommitInfos(state); err != nil {
- return err
- }
- }
- return nil
- }
-
- func getNextCommitInfos(state *getCommitInfoState) error {
- logOutput, err := logCommand(state.lastCommitHash, state).RunInDir(state.headCommit.repo.Path)
- if err != nil {
- return err
- }
- lines := strings.Split(logOutput, "\n")
- i := 0
- for i < len(lines) {
- state.nextCommit(lines[i])
- i++
- for ; i < len(lines); i++ {
- path := lines[i]
- if path == "" {
- break
- }
- state.update(path)
- }
- i++ // skip blank line
- if len(state.entries) == len(state.commits) {
- break
- }
- }
- return nil
- }
-
- func logCommand(exclusiveStartHash string, state *getCommitInfoState) *Command {
- var commitHash string
- if len(exclusiveStartHash) == 0 {
- commitHash = "HEAD"
- } else {
- commitHash = exclusiveStartHash + "^"
- }
- var command *Command
- numRemainingEntries := len(state.entries) - len(state.commits)
- if numRemainingEntries < 32 {
- searchSize := (numRemainingEntries + 1) / 2
- command = NewCommand("log", prettyLogFormat, "--name-only",
- "-"+strconv.Itoa(searchSize), commitHash, "--")
- for path, entry := range state.entries {
- if _, ok := state.commits[entry.Name()]; !ok {
- command.AddArguments(path)
- }
- }
- } else {
- command = NewCommand("log", prettyLogFormat, "--name-only",
- "-"+strconv.Itoa(state.nextSearchSize), commitHash, "--", state.treePath)
- }
- state.nextSearchSize += state.nextSearchSize
- return command
- }
|