-
Notifications
You must be signed in to change notification settings - Fork 25
/
fetch.go
158 lines (148 loc) · 5.09 KB
/
fetch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package main
import (
"bytes"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/cryptix/exp/git"
"github.com/pkg/errors"
)
// "fetch $sha1 $ref" method 1 - unpacking loose objects
// - look for it in ".git/objects/substr($sha1, 0, 2)/substr($sha, 2)"
// - if found, download it and put it in place. (there may be a command for this)
// - done \o/
func fetchObject(sha1 string) error {
return recurseCommit(sha1)
}
func recurseCommit(sha1 string) error {
obj, err := fetchAndWriteObj(sha1)
if err != nil {
return errors.Wrapf(err, "fetchAndWriteObj(%s) commit object failed", sha1)
}
commit, ok := obj.Commit()
if !ok {
return errors.Errorf("sha1<%s> is not a git commit object:%s ", sha1, obj)
}
if commit.Parent != "" {
if err := recurseCommit(commit.Parent); err != nil {
return errors.Wrapf(err, "recurseCommit(%s) commit Parent failed", commit.Parent)
}
}
return fetchTree(commit.Tree)
}
func fetchTree(sha1 string) error {
obj, err := fetchAndWriteObj(sha1)
if err != nil {
return errors.Wrapf(err, "fetchAndWriteObj(%s) commit tree failed", sha1)
}
entries, ok := obj.Tree()
if !ok {
return errors.Errorf("sha1<%s> is not a git tree object:%s ", sha1, obj)
}
for _, t := range entries {
obj, err := fetchAndWriteObj(t.SHA1Sum.String())
if err != nil {
return errors.Wrapf(err, "fetchAndWriteObj(%s) commit tree failed", sha1)
}
if obj.Type != git.BlobT {
return errors.Errorf("sha1<%s> is not a git tree object:%s ", t.SHA1Sum.String(), obj)
}
}
return nil
}
// fetchAndWriteObj looks for the loose object under 'thisGitRepo' global git dir
// and usses an io.TeeReader to write it to the local repo
func fetchAndWriteObj(sha1 string) (*git.Object, error) {
p := filepath.Join(ipfsRepoPath, "objects", sha1[:2], sha1[2:])
ipfsCat, err := ipfsShell.Cat(p)
if err != nil {
return nil, errors.Wrapf(err, "shell.Cat() commit failed")
}
targetP := filepath.Join(thisGitRepo, "objects", sha1[:2], sha1[2:])
if err := os.MkdirAll(filepath.Join(thisGitRepo, "objects", sha1[:2]), 0700); err != nil {
return nil, errors.Wrapf(err, "mkDirAll() failed")
}
targetObj, err := os.Create(targetP)
if err != nil {
return nil, errors.Wrapf(err, "os.Create(%s) commit failed", targetP)
}
obj, err := git.DecodeObject(io.TeeReader(ipfsCat, targetObj))
if err != nil {
return nil, errors.Wrapf(err, "git.DecodeObject(commit) failed")
}
if err := ipfsCat.Close(); err != nil {
err = errors.Wrap(err, "ipfs/cat Close failed")
if errRm := os.Remove(targetObj.Name()); errRm != nil {
err = errors.Wrapf(err, "failed removing targetObj: %s", errRm)
return nil, err
}
return nil, errors.Wrapf(err, "closing ipfs cat failed")
}
if err := targetObj.Close(); err != nil {
return nil, errors.Wrapf(err, "target file close() failed")
}
return obj, nil
}
// "fetch $sha1 $ref" method 2 - unpacking packed objects
// - look for it in packfiles by fetching ".git/objects/pack/*.idx"
// and looking at each idx with cat <idx> | git show-index (alternatively can learn to read the format in go)
// - if found in an <idx>, download the relevant .pack file,
// and feed it into `git index-pack --stdin --fix-thin` which will put it into place.
// - done \o/
func fetchPackedObject(sha1 string) error {
// search for all index files
packPath := filepath.Join(ipfsRepoPath, "objects", "pack")
links, err := ipfsShell.List(packPath)
if err != nil {
return errors.Wrapf(err, "shell FileList(%q) failed", packPath)
}
var indexes []string
for _, lnk := range links {
if lnk.Type == 2 && strings.HasSuffix(lnk.Name, ".idx") {
indexes = append(indexes, filepath.Join(packPath, lnk.Name))
}
}
if len(indexes) == 0 {
return errors.New("fetchPackedObject: no idx files found")
}
for _, idx := range indexes {
idxF, err := ipfsShell.Cat(idx)
if err != nil {
return errors.Wrapf(err, "fetchPackedObject: idx<%s> cat(%s) failed", sha1, idx)
}
// using external git show-index < idxF for now
// TODO: parse index file in go to make this portable
var b bytes.Buffer
showIdx := exec.Command("git", "show-index")
showIdx.Stdin = idxF
showIdx.Stdout = &b
showIdx.Stderr = &b
if err := showIdx.Run(); err != nil {
return errors.Wrapf(err, "fetchPackedObject: idx<%s> show-index start failed", sha1)
}
cmdOut := b.String()
if !strings.Contains(cmdOut, sha1) {
log.Log("idx", filepath.Base(idx), "event", "debug", "msg", "git show-index: sha1 not in index, next idx file")
continue
}
// we found an index with our hash inside
pack := strings.Replace(idx, ".idx", ".pack", 1)
packF, err := ipfsShell.Cat(pack)
if err != nil {
return errors.Wrapf(err, "fetchPackedObject: pack<%s> open() failed", sha1)
}
b.Reset()
unpackIdx := exec.Command("git", "unpack-objects")
unpackIdx.Dir = thisGitRepo // GIT_DIR
unpackIdx.Stdin = packF
unpackIdx.Stdout = &b
unpackIdx.Stderr = &b
if err := unpackIdx.Run(); err != nil {
return errors.Wrapf(err, "fetchPackedObject: pack<%s> 'git unpack-objects' failed\nOutput: %s", sha1, b.String())
}
return nil
}
return errors.Errorf("did not find sha1<%s> in %d index files", sha1, len(indexes))
}