| @@ -14,6 +14,7 @@ import ( | |||||
| "io/ioutil" | "io/ioutil" | ||||
| "os" | "os" | ||||
| "os/exec" | "os/exec" | ||||
| "strconv" | |||||
| "strings" | "strings" | ||||
| "code.gitea.io/git" | "code.gitea.io/git" | ||||
| @@ -368,8 +369,15 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D | |||||
| a := line[beg+2 : middle] | a := line[beg+2 : middle] | ||||
| b := line[middle+3:] | b := line[middle+3:] | ||||
| if hasQuote { | 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{ | 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 | // 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 | return updates, nil | ||||
| } | } | ||||
| @@ -276,7 +250,7 @@ func parseGitLsTreeOutput(stdout string) ([]fileUpdate, error) { | |||||
| func genesisChanges(repo *Repository, revision string) (*repoChanges, error) { | func genesisChanges(repo *Repository, revision string) (*repoChanges, error) { | ||||
| var changes repoChanges | var changes repoChanges | ||||
| stdout, err := git.NewCommand("ls-tree", "--full-tree", "-r", revision). | stdout, err := git.NewCommand("ls-tree", "--full-tree", "-r", revision). | ||||
| RunInDir(repo.RepoPath()) | |||||
| RunInDirBytes(repo.RepoPath()) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -327,11 +301,11 @@ func nonGenesisChanges(repo *Repository, revision string) (*repoChanges, error) | |||||
| cmd := git.NewCommand("ls-tree", "--full-tree", revision, "--") | cmd := git.NewCommand("ls-tree", "--full-tree", revision, "--") | ||||
| cmd.AddArguments(updatedFilenames...) | cmd.AddArguments(updatedFilenames...) | ||||
| stdout, err = cmd.RunInDir(repo.RepoPath()) | |||||
| lsTreeStdout, err := cmd.RunInDirBytes(repo.RepoPath()) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| changes.Updates, err = parseGitLsTreeOutput(stdout) | |||||
| changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout) | |||||
| return &changes, err | 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 | package git | ||||
| import ( | import ( | ||||
| "bytes" | |||||
| "fmt" | |||||
| "strings" | "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 | // SubTree get a sub tree by the sub dir path | ||||
| func (t *Tree) SubTree(rpath string) (*Tree, error) { | func (t *Tree) SubTree(rpath string) (*Tree, error) { | ||||
| if len(rpath) == 0 { | if len(rpath) == 0 { | ||||
| @@ -142,12 +62,11 @@ func (t *Tree) ListEntries() (Entries, error) { | |||||
| if t.entriesParsed { | if t.entriesParsed { | ||||
| return t.entries, nil | return t.entries, nil | ||||
| } | } | ||||
| t.entriesParsed = true | |||||
| stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path) | stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path) | ||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| t.entries, err = parseTreeData(t, stdout) | |||||
| t.entries, err = parseTreeEntries(stdout, t) | |||||
| return t.entries, err | return t.entries, err | ||||
| } | } | ||||
| @@ -3,10 +3,10 @@ | |||||
| "ignore": "test appengine", | "ignore": "test appengine", | ||||
| "package": [ | "package": [ | ||||
| { | { | ||||
| "checksumSHA1": "Gz+a5Qo4PCiB/Gf2f02v8HEAxDM=", | |||||
| "checksumSHA1": "j6YyQxuOYRs94MVEamvnbE6ZtD0=", | |||||
| "path": "code.gitea.io/git", | "path": "code.gitea.io/git", | ||||
| "revision": "6798d0f202cdc7187c00a467b586a4bdee27e8c9", | |||||
| "revisionTime": "2018-01-14T14:37:32Z" | |||||
| "revision": "827f97aaaa6a4ab5c31b1b799c56687a8cf6aade", | |||||
| "revisionTime": "2018-02-10T03:05:43Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "Qtq0kW+BnpYMOriaoCjMa86WGG8=", | "checksumSHA1": "Qtq0kW+BnpYMOriaoCjMa86WGG8=", | ||||