@@ -14,6 +14,7 @@ import ( | |||
"io/ioutil" | |||
"os" | |||
"os/exec" | |||
"strconv" | |||
"strings" | |||
"code.gitea.io/git" | |||
@@ -368,8 +369,15 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D | |||
a := line[beg+2 : middle] | |||
b := line[middle+3:] | |||
if hasQuote { | |||
a = string(git.UnescapeChars([]byte(a[1 : len(a)-1]))) | |||
b = string(git.UnescapeChars([]byte(b[1 : len(b)-1]))) | |||
var err error | |||
a, err = strconv.Unquote(a) | |||
if err != nil { | |||
return nil, fmt.Errorf("Unquote: %v", err) | |||
} | |||
b, err = strconv.Unquote(b) | |||
if err != nil { | |||
return nil, fmt.Errorf("Unquote: %v", err) | |||
} | |||
} | |||
curFile = &DiffFile{ |
@@ -231,43 +231,17 @@ func addDelete(filename string, repo *Repository, batch rupture.FlushingBatch) e | |||
} | |||
// parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command | |||
func parseGitLsTreeOutput(stdout string) ([]fileUpdate, error) { | |||
lines := strings.Split(stdout, "\n") | |||
updates := make([]fileUpdate, 0, len(lines)) | |||
for _, line := range lines { | |||
// expect line to be "<mode> <object-type> <object-sha>\t<filename>" | |||
line = strings.TrimSpace(line) | |||
if len(line) == 0 { | |||
continue | |||
} | |||
firstSpaceIndex := strings.IndexByte(line, ' ') | |||
if firstSpaceIndex < 0 { | |||
log.Error(4, "Misformatted git ls-tree output: %s", line) | |||
continue | |||
} | |||
tabIndex := strings.IndexByte(line, '\t') | |||
if tabIndex < 42+firstSpaceIndex || tabIndex == len(line)-1 { | |||
log.Error(4, "Misformatted git ls-tree output: %s", line) | |||
continue | |||
} | |||
if objectType := line[firstSpaceIndex+1 : tabIndex-41]; objectType != "blob" { | |||
// submodules appear as commit objects, we do not index submodules | |||
continue | |||
} | |||
blobSha := line[tabIndex-40 : tabIndex] | |||
filename := line[tabIndex+1:] | |||
if filename[0] == '"' { | |||
var err error | |||
filename, err = strconv.Unquote(filename) | |||
if err != nil { | |||
return nil, err | |||
} | |||
func parseGitLsTreeOutput(stdout []byte) ([]fileUpdate, error) { | |||
entries, err := git.ParseTreeEntries(stdout) | |||
if err != nil { | |||
return nil, err | |||
} | |||
updates := make([]fileUpdate, len(entries)) | |||
for i, entry := range entries { | |||
updates[i] = fileUpdate{ | |||
Filename: entry.Name(), | |||
BlobSha: entry.ID.String(), | |||
} | |||
updates = append(updates, fileUpdate{ | |||
Filename: filename, | |||
BlobSha: blobSha, | |||
}) | |||
} | |||
return updates, nil | |||
} | |||
@@ -276,7 +250,7 @@ func parseGitLsTreeOutput(stdout string) ([]fileUpdate, error) { | |||
func genesisChanges(repo *Repository, revision string) (*repoChanges, error) { | |||
var changes repoChanges | |||
stdout, err := git.NewCommand("ls-tree", "--full-tree", "-r", revision). | |||
RunInDir(repo.RepoPath()) | |||
RunInDirBytes(repo.RepoPath()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -327,11 +301,11 @@ func nonGenesisChanges(repo *Repository, revision string) (*repoChanges, error) | |||
cmd := git.NewCommand("ls-tree", "--full-tree", revision, "--") | |||
cmd.AddArguments(updatedFilenames...) | |||
stdout, err = cmd.RunInDir(repo.RepoPath()) | |||
lsTreeStdout, err := cmd.RunInDirBytes(repo.RepoPath()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
changes.Updates, err = parseGitLsTreeOutput(stdout) | |||
changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout) | |||
return &changes, err | |||
} | |||
@@ -0,0 +1,81 @@ | |||
// Copyright 2018 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. | |||
package git | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"strconv" | |||
) | |||
// ParseTreeEntries parses the output of a `git ls-tree` command. | |||
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { | |||
return parseTreeEntries(data, nil) | |||
} | |||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { | |||
entries := make([]*TreeEntry, 0, 10) | |||
for pos := 0; pos < len(data); { | |||
// expect line to be of the form "<mode> <type> <sha>\t<filename>" | |||
entry := new(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 | |||
pos += 12 // skip over "100644 blob " | |||
case "100755": | |||
entry.mode = EntryModeExec | |||
entry.Type = ObjectBlob | |||
pos += 12 // skip over "100755 blob " | |||
case "120000": | |||
entry.mode = EntryModeSymlink | |||
entry.Type = ObjectBlob | |||
pos += 12 // skip over "120000 blob " | |||
case "160000": | |||
entry.mode = EntryModeCommit | |||
entry.Type = ObjectCommit | |||
pos += 14 // skip over "160000 object " | |||
case "040000": | |||
entry.mode = EntryModeTree | |||
entry.Type = ObjectTree | |||
pos += 12 // skip over "040000 tree " | |||
default: | |||
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6])) | |||
} | |||
if pos+40 > len(data) { | |||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) | |||
} | |||
id, err := NewIDFromString(string(data[pos : pos+40])) | |||
if err != nil { | |||
return nil, fmt.Errorf("Invalid ls-tree output: %v", err) | |||
} | |||
entry.ID = id | |||
pos += 41 // skip over sha and trailing space | |||
end := pos + bytes.IndexByte(data[pos:], '\n') | |||
if end < pos { | |||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) | |||
} | |||
// 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])) | |||
if err != nil { | |||
return nil, fmt.Errorf("Invalid ls-tree output: %v", err) | |||
} | |||
} else { | |||
entry.name = string(data[pos:end]) | |||
} | |||
pos = end + 1 | |||
entries = append(entries, entry) | |||
} | |||
return entries, nil | |||
} |
@@ -5,8 +5,6 @@ | |||
package git | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"strings" | |||
) | |||
@@ -30,84 +28,6 @@ func NewTree(repo *Repository, id SHA1) *Tree { | |||
} | |||
} | |||
var escapeChar = []byte("\\") | |||
// UnescapeChars reverses escaped characters. | |||
func UnescapeChars(in []byte) []byte { | |||
if bytes.Index(in, escapeChar) == -1 { | |||
return in | |||
} | |||
endIdx := len(in) - 1 | |||
isEscape := false | |||
out := make([]byte, 0, endIdx+1) | |||
for i := range in { | |||
if in[i] == '\\' && !isEscape { | |||
isEscape = true | |||
continue | |||
} | |||
isEscape = false | |||
out = append(out, in[i]) | |||
} | |||
return out | |||
} | |||
// parseTreeData parses tree information from the (uncompressed) raw | |||
// data from the tree object. | |||
func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) { | |||
entries := make([]*TreeEntry, 0, 10) | |||
l := len(data) | |||
pos := 0 | |||
for pos < l { | |||
entry := new(TreeEntry) | |||
entry.ptree = tree | |||
step := 6 | |||
switch string(data[pos : pos+step]) { | |||
case "100644": | |||
entry.mode = EntryModeBlob | |||
entry.Type = ObjectBlob | |||
case "100755": | |||
entry.mode = EntryModeExec | |||
entry.Type = ObjectBlob | |||
case "120000": | |||
entry.mode = EntryModeSymlink | |||
entry.Type = ObjectBlob | |||
case "160000": | |||
entry.mode = EntryModeCommit | |||
entry.Type = ObjectCommit | |||
step = 8 | |||
case "040000": | |||
entry.mode = EntryModeTree | |||
entry.Type = ObjectTree | |||
default: | |||
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+step])) | |||
} | |||
pos += step + 6 // Skip string type of entry type. | |||
step = 40 | |||
id, err := NewIDFromString(string(data[pos : pos+step])) | |||
if err != nil { | |||
return nil, err | |||
} | |||
entry.ID = id | |||
pos += step + 1 // Skip half of SHA1. | |||
step = bytes.IndexByte(data[pos:], '\n') | |||
// In case entry name is surrounded by double quotes(it happens only in git-shell). | |||
if data[pos] == '"' { | |||
entry.name = string(UnescapeChars(data[pos+1 : pos+step-1])) | |||
} else { | |||
entry.name = string(data[pos : pos+step]) | |||
} | |||
pos += step + 1 | |||
entries = append(entries, entry) | |||
} | |||
return entries, nil | |||
} | |||
// SubTree get a sub tree by the sub dir path | |||
func (t *Tree) SubTree(rpath string) (*Tree, error) { | |||
if len(rpath) == 0 { | |||
@@ -142,12 +62,11 @@ func (t *Tree) ListEntries() (Entries, error) { | |||
if t.entriesParsed { | |||
return t.entries, nil | |||
} | |||
t.entriesParsed = true | |||
stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path) | |||
if err != nil { | |||
return nil, err | |||
} | |||
t.entries, err = parseTreeData(t, stdout) | |||
t.entries, err = parseTreeEntries(stdout, t) | |||
return t.entries, err | |||
} |
@@ -3,10 +3,10 @@ | |||
"ignore": "test appengine", | |||
"package": [ | |||
{ | |||
"checksumSHA1": "Gz+a5Qo4PCiB/Gf2f02v8HEAxDM=", | |||
"checksumSHA1": "j6YyQxuOYRs94MVEamvnbE6ZtD0=", | |||
"path": "code.gitea.io/git", | |||
"revision": "6798d0f202cdc7187c00a467b586a4bdee27e8c9", | |||
"revisionTime": "2018-01-14T14:37:32Z" | |||
"revision": "827f97aaaa6a4ab5c31b1b799c56687a8cf6aade", | |||
"revisionTime": "2018-02-10T03:05:43Z" | |||
}, | |||
{ | |||
"checksumSHA1": "Qtq0kW+BnpYMOriaoCjMa86WGG8=", |