Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
7.1 KiB
291 lines
7.1 KiB
// 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 ( |
|
"os" |
|
"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, os.PathSeparator); 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 = state.headCommit.ID.String() |
|
} 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 |
|
}
|
|
|