├── .github ├── actions │ └── go-test-setup │ │ └── action.yml └── workflows │ ├── generated-pr.yml │ ├── go-check.yml │ ├── go-test-config.json │ ├── go-test.yml │ ├── release-check.yml │ ├── releaser.yml │ ├── stale.yml │ └── tagpush.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd └── git-remote-ipld │ ├── ipld.go │ ├── ipld_test.go │ └── main.go ├── core ├── fetch.go ├── push.go ├── remote.go ├── tracker.go └── util.go ├── doc.go ├── go.mod ├── go.sum ├── mock └── git │ ├── COMMIT_EDITMSG │ ├── HEAD │ ├── config │ ├── description │ ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── prepare-commit-msg.sample │ └── update.sample │ ├── index │ ├── info │ └── exclude │ ├── logs │ ├── HEAD │ └── refs │ │ └── heads │ │ ├── french │ │ ├── italian │ │ └── master │ ├── objects │ ├── 16 │ │ └── 2429cc0dac923dff140ec29247f42a8e362419 │ ├── 31 │ │ └── f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00 │ ├── 45 │ │ └── 306d9e3a93e015de956ec7be2b3fb377d3ec85 │ ├── 58 │ │ └── 1caa0fe56cf01dc028cc0b089d364993e046b6 │ ├── 78 │ │ └── a77abd233c24d8e6a0d0d040c79ae569fc7a19 │ ├── 95 │ │ └── 3ee2f1c6a92eeff7604e9a5e6c93b1d28615c8 │ ├── 98 │ │ ├── 0a0d5f19a64b4b30a87d4206aade58726b60e3 │ │ └── 991eb8d6cb267d0ccc296c29b109d3d062da76 │ ├── 04 │ │ └── 47b05c9be04cd29f437f2ec98af9b5a6a17156 │ ├── a1 │ │ └── 0a342f7ead6bcea5a7564b2f3cee749063ad60 │ ├── d5 │ │ └── b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 │ └── ee │ │ └── bcc8ed1a3d42b62a8996e54d39e2d831ab5570 │ └── refs │ └── heads │ ├── french │ ├── italian │ └── master ├── util ├── compare.go └── copy.go └── version.json /.github/actions/go-test-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Start IPFS Daemon 2 | description: Start IPFS daemon and wait for it to become ready 3 | 4 | runs: 5 | using: 'composite' 6 | steps: 7 | - uses: ipfs/download-ipfs-distribution-action@v1 8 | - uses: ipfs/start-ipfs-daemon-action@v1 9 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/go-check.yml: -------------------------------------------------------------------------------- 1 | name: Go Checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["master"] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | go-check: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0 19 | -------------------------------------------------------------------------------- /.github/workflows/go-test-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "shuffle": false 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Go Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["master"] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | go-test: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0 19 | secrets: 20 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/release-check.yml: -------------------------------------------------------------------------------- 1 | name: Release Checker 2 | 3 | on: 4 | pull_request_target: 5 | paths: [ 'version.json' ] 6 | types: [ opened, synchronize, reopened, labeled, unlabeled ] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | release-check: 19 | uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0 20 | -------------------------------------------------------------------------------- /.github/workflows/releaser.yml: -------------------------------------------------------------------------------- 1 | name: Releaser 2 | 3 | on: 4 | push: 5 | paths: [ 'version.json' ] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | releaser: 17 | uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0 18 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/tagpush.yml: -------------------------------------------------------------------------------- 1 | name: Tag Push Checker 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | permissions: 9 | contents: read 10 | issues: write 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | releaser: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/git-remote-ipns/git-remote-ipns 2 | cmd/git-remote-ipld/git-remote-ipld -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Łukasz Magiera. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | go build -o cmd/git-remote-ipld/git-remote-ipld ./cmd/git-remote-ipld/... 3 | 4 | test: 5 | go test -v ./... 6 | 7 | .PHONY: all test 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git IPLD remote helper 2 | 3 | Push and fetch commits using IPFS! 4 | 5 | This helper is experimental as of now 6 | 7 | ## Usage 8 | ``` 9 | Clone an example repository: 10 | $ git clone ipld://2347e110c29742a1783134ef45f5bff58b29e40e 11 | 12 | Pull a commit: 13 | $ git pull ipld://2347e110c29742a1783134ef45f5bff58b29e40e 14 | 15 | Push: 16 | $ git push --set-upstream ipld:// master 17 | ``` 18 | 19 | Note: Some features like remote tracking are still missing, though the plugin is 20 | quite usable. IPNS helper is WIP and doesn't yet do what it should 21 | 22 | ## Installation 23 | 1. `go get github.com/ipfs-shipyard/git-remote-ipld` 24 | 2. `make install` 25 | 3. Done 26 | 5. Make sure you run go-ipfs 0.4.17 or newer as you need git support 27 | 28 | ## Limitations / TODOs 29 | * ipns remote is not implemented fully yet 30 | 31 | # Troubleshooting 32 | * `fetch: manifest has unsupported version: 2 (we support 3)` on any command 33 | - This usually means that tracker data format has changed 34 | - Simply do `rm -rf .git/ipld` 35 | 36 | ## License 37 | MIT 38 | -------------------------------------------------------------------------------- /cmd/git-remote-ipld/ipld.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "path" 9 | "strings" 10 | 11 | core "github.com/ipfs-shipyard/git-remote-ipld/core" 12 | ipfs "github.com/ipfs/go-ipfs-api" 13 | 14 | "github.com/ipfs/go-cid" 15 | "gopkg.in/src-d/go-git.v4/plumbing" 16 | ) 17 | 18 | const ( 19 | LARGE_OBJECT_DIR = "objects" 20 | LOBJ_TRACKER_PRIFIX = "//lobj" 21 | ) 22 | 23 | const ( 24 | REFPATH_HEAD = iota 25 | REFPATH_REF 26 | ) 27 | 28 | type refPath struct { 29 | path string 30 | rType int 31 | 32 | hash string 33 | } 34 | 35 | type IpnsHandler struct { 36 | api *ipfs.Shell 37 | 38 | remoteName string 39 | currentHash string 40 | 41 | largeObjs map[string]string 42 | 43 | didPush bool 44 | } 45 | 46 | func (h *IpnsHandler) Initialize(remote *core.Remote) error { 47 | h.api = ipfs.NewLocalShell() 48 | h.currentHash = h.remoteName 49 | return nil 50 | } 51 | 52 | func (h *IpnsHandler) Finish(remote *core.Remote) error { 53 | //TODO: publish 54 | if h.didPush { 55 | if err := h.fillMissingLobjs(remote.Tracker); err != nil { 56 | return err 57 | } 58 | 59 | remote.Logger.Printf("Pushed to IPFS as \x1b[32mipld://%s\x1b[39m\n", h.currentHash) 60 | } 61 | return nil 62 | } 63 | 64 | func (h *IpnsHandler) ProvideBlock(cid string, tracker *core.Tracker) ([]byte, error) { 65 | if h.largeObjs == nil { 66 | if err := h.loadObjectMap(); err != nil { 67 | return nil, err 68 | } 69 | } 70 | 71 | mappedCid, ok := h.largeObjs[cid] 72 | if !ok { 73 | return nil, core.ErrNotProvided 74 | } 75 | 76 | if err := tracker.Set(LOBJ_TRACKER_PRIFIX+"/"+cid, []byte(mappedCid)); err != nil { 77 | return nil, err 78 | } 79 | 80 | r, err := h.api.Cat(fmt.Sprintf("/ipfs/%s", mappedCid)) 81 | if err != nil { 82 | return nil, errors.New("cat error") 83 | } 84 | 85 | data, err := ioutil.ReadAll(r) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | realCid, err := h.api.DagPut(data, "raw", "git") 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | if realCid != cid { 96 | return nil, fmt.Errorf("unexpected cid for provided block %s != %s", realCid, cid) 97 | } 98 | 99 | return data, nil 100 | } 101 | 102 | func (h *IpnsHandler) loadObjectMap() error { 103 | h.largeObjs = map[string]string{} 104 | 105 | links, err := h.api.List(h.currentHash + "/" + LARGE_OBJECT_DIR) 106 | if err != nil { 107 | //TODO: Find a better way with coreapi 108 | if isNoLink(err) { 109 | return nil 110 | } 111 | return err 112 | } 113 | 114 | for _, link := range links { 115 | h.largeObjs[link.Name] = link.Hash 116 | } 117 | 118 | return nil 119 | } 120 | 121 | func (h *IpnsHandler) List(remote *core.Remote, forPush bool) ([]string, error) { 122 | out := make([]string, 0) 123 | if !forPush { 124 | refs, err := h.paths(h.api, h.remoteName, 0) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | for _, ref := range refs { 130 | switch ref.rType { 131 | case REFPATH_HEAD: 132 | r := path.Join(strings.Split(ref.path, "/")[1:]...) 133 | c, err := cid.Parse(ref.hash) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | hash, err := core.HexFromCid(c) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | out = append(out, fmt.Sprintf("%s %s", hash, r)) 144 | case REFPATH_REF: 145 | r := path.Join(strings.Split(ref.path, "/")[1:]...) 146 | dest, err := h.getRef(r) 147 | if err != nil { 148 | return nil, err 149 | } 150 | out = append(out, fmt.Sprintf("@%s %s", dest, r)) 151 | } 152 | 153 | } 154 | } else { 155 | it, err := remote.Repo.Branches() 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | err = it.ForEach(func(ref *plumbing.Reference) error { 161 | remoteRef := "0000000000000000000000000000000000000000" 162 | 163 | localRef, err := h.api.ResolvePath(path.Join(h.currentHash, ref.Name().String())) 164 | if err != nil && !isNoLink(err) { 165 | return err 166 | } 167 | if err == nil { 168 | refCid, err := cid.Parse(localRef) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | remoteRef, err = core.HexFromCid(refCid) 174 | if err != nil { 175 | return err 176 | } 177 | } 178 | 179 | out = append(out, fmt.Sprintf("%s %s", remoteRef, ref.Name())) 180 | 181 | return nil 182 | }) 183 | if err != nil { 184 | return nil, err 185 | } 186 | } 187 | 188 | return out, nil 189 | } 190 | 191 | func (h *IpnsHandler) Push(remote *core.Remote, local string, remoteRef string) (string, error) { 192 | h.didPush = true 193 | 194 | localRef, err := remote.Repo.Reference(plumbing.ReferenceName(local), true) 195 | if err != nil { 196 | return "", fmt.Errorf("command push: %v", err) 197 | } 198 | 199 | headHash := localRef.Hash().String() 200 | 201 | push := remote.NewPush() 202 | push.NewNode = h.bigNodePatcher(remote.Tracker) 203 | 204 | err = push.PushHash(headHash) 205 | if err != nil { 206 | return "", fmt.Errorf("command push: %v", err) 207 | } 208 | 209 | hash := localRef.Hash() 210 | remote.Tracker.Set(remoteRef, (&hash)[:]) 211 | 212 | c, err := core.CidFromHex(headHash) 213 | if err != nil { 214 | return "", fmt.Errorf("push: %v", err) 215 | } 216 | 217 | //patch object 218 | h.currentHash, err = h.api.PatchLink(h.currentHash, remoteRef, c.String(), true) 219 | if err != nil { 220 | return "", fmt.Errorf("push: %v", err) 221 | } 222 | 223 | head, err := h.getRef("HEAD") 224 | if err != nil { 225 | return "", fmt.Errorf("push: %v", err) 226 | } 227 | if head == "" { 228 | headRef, err := h.api.Add(strings.NewReader("refs/heads/master")) //TODO: Make this smarter? 229 | if err != nil { 230 | return "", fmt.Errorf("push: %v", err) 231 | } 232 | 233 | h.currentHash, err = h.api.PatchLink(h.currentHash, "HEAD", headRef, true) 234 | if err != nil { 235 | return "", fmt.Errorf("push: %v", err) 236 | } 237 | } 238 | 239 | return local, nil 240 | } 241 | 242 | // bigNodePatcher returns a function which patches large object mapping into 243 | // the resulting object 244 | func (h *IpnsHandler) bigNodePatcher(tracker *core.Tracker) func(cid.Cid, []byte) error { 245 | return func(hash cid.Cid, data []byte) error { 246 | if len(data) > (1 << 21) { 247 | c, err := h.api.Add(bytes.NewReader(data)) 248 | if err != nil { 249 | return err 250 | } 251 | 252 | if err := tracker.Set(LOBJ_TRACKER_PRIFIX+"/"+hash.String(), []byte(c)); err != nil { 253 | return err 254 | } 255 | 256 | h.currentHash, err = h.api.PatchLink(h.currentHash, "objects/"+hash.String(), c, true) 257 | if err != nil { 258 | return err 259 | } 260 | } 261 | 262 | return nil 263 | } 264 | } 265 | 266 | func (h *IpnsHandler) fillMissingLobjs(tracker *core.Tracker) error { 267 | if h.largeObjs == nil { 268 | if err := h.loadObjectMap(); err != nil { 269 | return err 270 | } 271 | } 272 | 273 | tracked, err := tracker.ListPrefixed(LOBJ_TRACKER_PRIFIX) 274 | if err != nil { 275 | return err 276 | } 277 | 278 | for k, v := range tracked { 279 | if _, has := h.largeObjs[k]; has { 280 | continue 281 | } 282 | 283 | k = strings.TrimPrefix(k, LOBJ_TRACKER_PRIFIX+"/") 284 | 285 | h.largeObjs[k] = v 286 | h.currentHash, err = h.api.PatchLink(h.currentHash, "objects/"+k, v, true) 287 | if err != nil { 288 | return err 289 | } 290 | } 291 | 292 | return nil 293 | } 294 | 295 | func (h *IpnsHandler) getRef(name string) (string, error) { 296 | r, err := h.api.Cat(path.Join(h.remoteName, name)) 297 | if err != nil { 298 | if isNoLink(err) { 299 | return "", nil 300 | } 301 | return "", err 302 | } 303 | defer r.Close() 304 | 305 | buf := new(bytes.Buffer) 306 | _, err = buf.ReadFrom(r) 307 | if err != nil { 308 | return "", err 309 | } 310 | 311 | return buf.String(), nil 312 | } 313 | 314 | func (h *IpnsHandler) paths(api *ipfs.Shell, p string, level int) ([]refPath, error) { 315 | links, err := api.List(p) 316 | if err != nil { 317 | return nil, err 318 | } 319 | 320 | out := make([]refPath, 0) 321 | for _, link := range links { 322 | switch link.Type { 323 | case ipfs.TDirectory: 324 | if level == 0 && link.Name == LARGE_OBJECT_DIR { 325 | continue 326 | } 327 | 328 | sub, err := h.paths(api, path.Join(p, link.Name), level+1) 329 | if err != nil { 330 | return nil, err 331 | } 332 | out = append(out, sub...) 333 | case ipfs.TFile: 334 | out = append(out, refPath{path.Join(p, link.Name), REFPATH_REF, link.Hash}) 335 | case -1, 0: //unknown, assume git node 336 | out = append(out, refPath{path.Join(p, link.Name), REFPATH_HEAD, link.Hash}) 337 | default: 338 | return nil, fmt.Errorf("unexpected link type %d", link.Type) 339 | } 340 | } 341 | return out, nil 342 | } 343 | 344 | func isNoLink(err error) bool { 345 | return strings.Contains(err.Error(), "no link named") || strings.Contains(err.Error(), "no link by that name") 346 | } 347 | -------------------------------------------------------------------------------- /cmd/git-remote-ipld/ipld_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows || !386 2 | // +build !windows !386 3 | 4 | // TestCapabilities fails on win32 machines because badger exits with: 5 | // The process cannot access the file because it is being used by another process. 6 | 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "io" 12 | "io/ioutil" 13 | "log" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "testing" 18 | 19 | "github.com/ipfs-shipyard/git-remote-ipld/util" 20 | ) 21 | 22 | func TestCapabilities(t *testing.T) { 23 | tmpdir := setupTest(t) 24 | defer os.RemoveAll(tmpdir) 25 | 26 | // git clone ipld::d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 27 | args := []string{"git-remote-ipld", "origin", "ipld://QmZVjKhhUrjodywbU4hpCL32M7CR7Sbow2MSqRpB3PGBUe"} 28 | 29 | listExp := []string{ 30 | "@refs/heads/master HEAD", 31 | "d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 refs/heads/master", 32 | } 33 | listForPushExp := []string{ 34 | "0000000000000000000000000000000000000000 refs/heads/french", 35 | "0000000000000000000000000000000000000000 refs/heads/italian", 36 | "d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 refs/heads/master", 37 | } 38 | 39 | testCase(t, args, "capabilities", []string{"push", "fetch"}) 40 | testCase(t, args, "list", listExp) 41 | testCase(t, args, "list for-push", listForPushExp) 42 | 43 | // mock/git> git push --set-upstream ipld:: master 44 | testCase(t, args, "push refs/heads/master:refs/heads/master", []string{}) 45 | 46 | testCase(t, args, "fetch d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 refs/heads/master\n", []string{""}) 47 | comparePullToMock(t, tmpdir, "git") 48 | } 49 | 50 | func testCase(t *testing.T, args []string, input string, expected []string) { 51 | reader := strings.NewReader(input + "\n") 52 | var writer bytes.Buffer 53 | logger := log.New(os.Stderr, "", 0) 54 | err := Main(args, reader, &writer, logger) 55 | if err != nil && err != io.EOF { 56 | t.Fatal(err) 57 | } 58 | 59 | response := writer.String() 60 | exp := strings.Join(expected, "\n") 61 | if strings.TrimSpace(response) != exp { 62 | t.Fatalf("Args: %s\nInput:\n%s\nExpected:\n%s\nActual:\n'%s'\n", args, input, exp, response) 63 | } 64 | } 65 | 66 | func comparePullToMock(t *testing.T, tmpdir, mock string) { 67 | wd, _ := os.Getwd() 68 | mockdir := filepath.Join(wd, "..", "..", "mock", mock) 69 | compareContents(t, filepath.Join(tmpdir, ".git"), mockdir) 70 | } 71 | 72 | func compareContents(t *testing.T, src, dst string) { 73 | src = filepath.Clean(src) 74 | dst = filepath.Clean(dst) 75 | err := util.CompareDirs(src, dst, []string{"ipld"}) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | } 80 | 81 | func setupTest(t *testing.T) string { 82 | wd, _ := os.Getwd() 83 | src := filepath.Join(wd, "..", "..", "mock", "git") 84 | si, err := os.Stat(src) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | if !si.IsDir() { 89 | t.Fatal("source is not a directory") 90 | } 91 | 92 | tmpdir, err := ioutil.TempDir("", "git-test") 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | 97 | dst := filepath.Join(tmpdir, ".git") 98 | err = util.CopyDir(src, dst) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | 103 | os.Setenv("GIT_DIR", dst) 104 | return tmpdir 105 | } 106 | -------------------------------------------------------------------------------- /cmd/git-remote-ipld/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/ipfs-shipyard/git-remote-ipld/core" 11 | ) 12 | 13 | const ( 14 | IPLD_PREFIX = "ipld://" 15 | IPFS_PREFIX = "ipfs://" 16 | 17 | EMPTY_REPO = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" 18 | ) 19 | 20 | func Main(args []string, reader io.Reader, writer io.Writer, logger *log.Logger) error { 21 | if len(args) < 3 { 22 | return fmt.Errorf("usage: git-remote-ipns remote-name url") 23 | } 24 | 25 | remoteName := args[2] 26 | if strings.HasPrefix(remoteName, IPLD_PREFIX) || strings.HasPrefix(remoteName, IPFS_PREFIX) { 27 | remoteName = remoteName[len(IPLD_PREFIX):] 28 | } 29 | 30 | if remoteName == "" { 31 | remoteName = EMPTY_REPO 32 | } 33 | 34 | remote, err := core.NewRemote(&IpnsHandler{remoteName: remoteName}, reader, writer, logger) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | if err := remote.ProcessCommands(); err != nil { 40 | err2 := remote.Close() 41 | if err2 != nil { 42 | return fmt.Errorf("%s; close error: %s", err, err2) 43 | } 44 | return err 45 | } 46 | 47 | return remote.Close() 48 | } 49 | 50 | func main() { 51 | if err := Main(os.Args, os.Stdin, os.Stdout, nil); err != nil { 52 | fmt.Fprintf(os.Stderr, "\x1b[K") 53 | log.Fatal(err) 54 | } 55 | fmt.Fprintf(os.Stderr, "Done\n") 56 | } 57 | -------------------------------------------------------------------------------- /core/fetch.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "path" 11 | "sync" 12 | 13 | "github.com/ipfs/go-cid" 14 | ipfs "github.com/ipfs/go-ipfs-api" 15 | "github.com/ipfs/go-ipld-git" 16 | mh "github.com/multiformats/go-multihash" 17 | "github.com/remeh/sizedwaitgroup" 18 | ) 19 | 20 | var ErrNotProvided = errors.New("block not provided") 21 | 22 | type ObjectProvider func(cid string, tracker *Tracker) ([]byte, error) 23 | 24 | type Fetch struct { 25 | objectDir string 26 | gitDir string 27 | 28 | done uint64 29 | todoc uint64 30 | 31 | todo chan string 32 | log *log.Logger 33 | tracker *Tracker 34 | 35 | errCh chan error 36 | wg sizedwaitgroup.SizedWaitGroup 37 | doneCh chan []byte 38 | 39 | fsLk *sync.Mutex 40 | 41 | provider ObjectProvider 42 | api *ipfs.Shell 43 | } 44 | 45 | func NewFetch(gitDir string, tracker *Tracker, provider ObjectProvider) *Fetch { 46 | return &Fetch{ 47 | objectDir: path.Join(gitDir, "objects"), 48 | gitDir: gitDir, 49 | 50 | log: log.New(os.Stderr, "fetch: ", 0), 51 | tracker: tracker, 52 | 53 | fsLk: &sync.Mutex{}, 54 | 55 | wg: sizedwaitgroup.New(512), 56 | 57 | //Note: logic below somewhat relies on these channels staying unbuffered 58 | todo: make(chan string), 59 | errCh: make(chan error), 60 | doneCh: make(chan []byte), 61 | 62 | provider: provider, 63 | api: ipfs.NewLocalShell(), 64 | } 65 | } 66 | 67 | func (f *Fetch) FetchHash(base string) error { 68 | go func() { 69 | f.todo <- base 70 | }() 71 | return f.doWork() 72 | } 73 | 74 | func (f *Fetch) doWork() error { 75 | for { 76 | select { 77 | case err := <-f.errCh: 78 | return err 79 | case hash := <-f.todo: 80 | f.todoc++ 81 | if err := f.processSingle(hash); err != nil { 82 | return err 83 | } 84 | case <-f.doneCh: 85 | f.done++ 86 | } 87 | 88 | f.log.Printf("%d/%d\r\x1b[A", f.done, f.todoc) 89 | 90 | if f.done == f.todoc { 91 | f.wg.Wait() 92 | f.log.Printf("\n") 93 | return nil 94 | } 95 | } 96 | } 97 | 98 | func (f *Fetch) processSingle(hash string) error { 99 | mhash, err := mh.FromHexString("1114" + hash) 100 | if err != nil { 101 | return fmt.Errorf("fetch: %v", err) 102 | } 103 | 104 | c := cid.NewCidV1(cid.GitRaw, mhash).String() 105 | 106 | sha, err := hex.DecodeString(hash) 107 | if err != nil { 108 | return fmt.Errorf("fetch: %v", err) 109 | } 110 | 111 | has, err := f.tracker.HasEntry(sha) 112 | if err != nil { 113 | return err 114 | } 115 | if has { 116 | f.todoc-- 117 | return nil 118 | } 119 | 120 | // Need to do this early 121 | if err := f.tracker.AddEntry(sha); err != nil { 122 | return fmt.Errorf("fetch: %v", err) 123 | } 124 | 125 | go func() { 126 | f.wg.Add() 127 | defer f.wg.Done() 128 | 129 | f.fsLk.Lock() 130 | objectPath, err := prepHashPath(f.objectDir, hash) 131 | f.fsLk.Unlock() 132 | if err != nil { 133 | f.errCh <- err 134 | return 135 | } 136 | 137 | object, err := f.provider(c, f.tracker) 138 | if err != nil { 139 | if err != ErrNotProvided { 140 | f.errCh <- err 141 | return 142 | } 143 | 144 | object, err = f.api.BlockGet(c) 145 | if err != nil { 146 | f.errCh <- fmt.Errorf("fetch: %v", err) 147 | return 148 | } 149 | } 150 | 151 | f.processLinks(object) 152 | 153 | object = compressObject(object) 154 | 155 | ///////////////// 156 | 157 | err = ioutil.WriteFile(*objectPath, object, 0444) 158 | if err != nil { 159 | f.errCh <- fmt.Errorf("fetch: %v", err) 160 | return 161 | } 162 | 163 | //TODO: see if moving this higher would help 164 | f.doneCh <- sha 165 | }() 166 | 167 | return nil 168 | } 169 | 170 | func (f *Fetch) processLinks(object []byte) error { 171 | nd, err := ipldgit.ParseObjectFromBuffer(object) 172 | if err != nil { 173 | return fmt.Errorf("fetch: %v", err) 174 | } 175 | 176 | links := nd.Links() 177 | for _, link := range links { 178 | mhash := link.Cid.Hash() 179 | hash := mhash.HexString()[4:] 180 | 181 | f.todo <- hash 182 | } 183 | return nil 184 | } 185 | 186 | func prepHashPath(localDir string, hash string) (*string, error) { 187 | base := path.Join(localDir, hash[:2]) 188 | err := os.MkdirAll(base, 0777) 189 | 190 | if err != nil { 191 | return nil, err 192 | } 193 | 194 | objectPath := path.Join(base, hash[2:]) 195 | return &objectPath, nil 196 | } 197 | -------------------------------------------------------------------------------- /core/push.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "container/list" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "os/signal" 12 | "path" 13 | 14 | cid "github.com/ipfs/go-cid" 15 | ipfs "github.com/ipfs/go-ipfs-api" 16 | ipldgit "github.com/ipfs/go-ipld-git" 17 | mh "github.com/multiformats/go-multihash" 18 | sizedwaitgroup "github.com/remeh/sizedwaitgroup" 19 | git "gopkg.in/src-d/go-git.v4" 20 | plumbing "gopkg.in/src-d/go-git.v4/plumbing" 21 | ) 22 | 23 | type Push struct { 24 | objectDir string 25 | gitDir string 26 | 27 | done uint64 28 | todoc uint64 29 | todo *list.List 30 | log *log.Logger 31 | tracker *Tracker 32 | repo *git.Repository 33 | 34 | processing map[string]int 35 | subs map[string][][]byte 36 | 37 | errCh chan error 38 | wg sizedwaitgroup.SizedWaitGroup 39 | 40 | NewNode func(hash cid.Cid, data []byte) error 41 | } 42 | 43 | func NewPush(gitDir string, tracker *Tracker, repo *git.Repository) *Push { 44 | return &Push{ 45 | objectDir: path.Join(gitDir, "objects"), 46 | gitDir: gitDir, 47 | 48 | todo: list.New(), 49 | log: log.New(os.Stderr, "push: ", 0), 50 | tracker: tracker, 51 | repo: repo, 52 | todoc: 1, 53 | 54 | processing: map[string]int{}, 55 | subs: map[string][][]byte{}, 56 | 57 | wg: sizedwaitgroup.New(512), 58 | errCh: make(chan error), 59 | } 60 | } 61 | 62 | func (p *Push) PushHash(hash string) error { 63 | p.todo.PushFront(hash) 64 | return p.doWork() 65 | } 66 | 67 | func (p *Push) doWork() error { 68 | defer p.wg.Wait() 69 | 70 | api := ipfs.NewLocalShell() 71 | 72 | intch := make(chan os.Signal, 1) 73 | signal.Notify(intch, os.Interrupt) 74 | go func() { 75 | <-intch 76 | p.errCh <- errors.New("interrupted") 77 | }() 78 | defer signal.Stop(intch) 79 | 80 | for e := p.todo.Front(); e != nil; e = e.Next() { 81 | if df, ok := e.Value.(func() error); ok { 82 | if err := df(); err != nil { 83 | return err 84 | } 85 | p.todoc-- 86 | continue 87 | } 88 | 89 | hash := e.Value.(string) 90 | 91 | sha, err := hex.DecodeString(hash) 92 | if err != nil { 93 | return fmt.Errorf("push: %v", err) 94 | } 95 | 96 | _, processing := p.processing[string(sha)] 97 | if processing { 98 | p.todoc-- 99 | continue 100 | } 101 | 102 | has, err := p.tracker.HasEntry(sha) 103 | if err != nil { 104 | return fmt.Errorf("push/process: %v", err) 105 | } 106 | 107 | if has { 108 | p.todoc-- 109 | continue 110 | } 111 | 112 | expectedCid, err := CidFromHex(hash) 113 | if err != nil { 114 | return fmt.Errorf("push: %v", err) 115 | } 116 | 117 | obj, err := p.repo.Storer.EncodedObject(plumbing.AnyObject, plumbing.NewHash(hash)) 118 | if err != nil { 119 | return fmt.Errorf("push/getObject(%s): %v", hash, err) 120 | } 121 | 122 | rawReader, err := obj.Reader() 123 | if err != nil { 124 | return fmt.Errorf("push: %v", err) 125 | } 126 | 127 | raw, err := ioutil.ReadAll(rawReader) 128 | if err != nil { 129 | return fmt.Errorf("push: %v", err) 130 | } 131 | 132 | switch obj.Type() { 133 | case plumbing.CommitObject: 134 | raw = append([]byte(fmt.Sprintf("commit %d\x00", obj.Size())), raw...) 135 | case plumbing.TreeObject: 136 | raw = append([]byte(fmt.Sprintf("tree %d\x00", obj.Size())), raw...) 137 | case plumbing.BlobObject: 138 | raw = append([]byte(fmt.Sprintf("blob %d\x00", obj.Size())), raw...) 139 | case plumbing.TagObject: 140 | raw = append([]byte(fmt.Sprintf("tag %d\x00", obj.Size())), raw...) 141 | } 142 | 143 | p.done++ 144 | if p.done%100 == 0 || p.done == p.todoc { 145 | p.log.Printf("%d/%d (P:%d) %s %s\r\x1b[A", p.done, p.todoc, len(p.processing), hash, expectedCid.String()) 146 | } 147 | 148 | p.wg.Add() 149 | go func() { 150 | defer p.wg.Done() 151 | 152 | res, err := api.BlockPut(raw, "git-raw", "sha1", -1) 153 | if err != nil { 154 | p.errCh <- fmt.Errorf("push/put: %v", err) 155 | return 156 | } 157 | 158 | if expectedCid.String() != res { 159 | p.errCh <- fmt.Errorf("CIDs don't match: expected %s, got %s", expectedCid.String(), res) 160 | return 161 | } 162 | 163 | if p.NewNode != nil { 164 | if err := p.NewNode(expectedCid, raw); err != nil { 165 | p.errCh <- fmt.Errorf("newNode: %s", err) 166 | return 167 | } 168 | } 169 | }() 170 | 171 | n, err := p.processLinks(raw, sha) 172 | if err != nil { 173 | return fmt.Errorf("push/processLinks: %v", err) 174 | } 175 | 176 | if n == 0 { 177 | p.todoc++ 178 | p.todo.PushBack(p.doneFunc(sha)) 179 | } else { 180 | p.processing[string(sha)] = n 181 | } 182 | 183 | select { 184 | case e := <-p.errCh: 185 | return e 186 | default: 187 | } 188 | } 189 | p.log.Printf("\n") 190 | return nil 191 | } 192 | 193 | func (p *Push) doneFunc(sha []byte) func() error { 194 | return func() error { 195 | if err := p.tracker.AddEntry(sha); err != nil { 196 | return err 197 | } 198 | delete(p.processing, string(sha)) 199 | 200 | for _, sub := range p.subs[string(sha)] { 201 | p.processing[string(sub)]-- 202 | if p.processing[string(sub)] <= 0 { 203 | p.todoc++ 204 | p.todo.PushBack(p.doneFunc(sub)) 205 | } 206 | } 207 | delete(p.subs, string(sha)) 208 | return nil 209 | } 210 | } 211 | 212 | func (p *Push) processLinks(object []byte, selfSha []byte) (int, error) { 213 | nd, err := ipldgit.ParseObjectFromBuffer(object) 214 | if err != nil { 215 | return 0, fmt.Errorf("push/process: %v", err) 216 | } 217 | 218 | var n int 219 | links := nd.Links() 220 | for _, link := range links { 221 | mhash := link.Cid.Hash() 222 | decoded, err := mh.Decode(mhash) 223 | if err != nil { 224 | return 0, fmt.Errorf("push/process: %v", err) 225 | } 226 | 227 | if _, proc := p.processing[string(decoded.Digest)]; !proc { 228 | has, err := p.tracker.HasEntry(decoded.Digest) 229 | if err != nil { 230 | return 0, fmt.Errorf("push/process: %v", err) 231 | } 232 | 233 | if has { 234 | continue 235 | } 236 | } 237 | 238 | p.subs[string(decoded.Digest)] = append(p.subs[string(decoded.Digest)], selfSha) 239 | 240 | n++ 241 | p.todoc++ 242 | p.todo.PushBack(hex.EncodeToString(decoded.Digest)) 243 | } 244 | return n, nil 245 | } 246 | -------------------------------------------------------------------------------- /core/remote.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path" 11 | "strings" 12 | 13 | git "gopkg.in/src-d/go-git.v4" 14 | ) 15 | 16 | type RemoteHandler interface { 17 | List(remote *Remote, forPush bool) ([]string, error) 18 | Push(remote *Remote, localRef string, remoteRef string) (string, error) 19 | 20 | Initialize(remote *Remote) error 21 | Finish(remote *Remote) error 22 | 23 | ProvideBlock(cid string, tracker *Tracker) ([]byte, error) 24 | } 25 | 26 | type Remote struct { 27 | reader io.Reader 28 | writer io.Writer 29 | Logger *log.Logger 30 | localDir string 31 | 32 | Repo *git.Repository 33 | Tracker *Tracker 34 | 35 | Handler RemoteHandler 36 | 37 | todo []func() (string, error) 38 | } 39 | 40 | func NewRemote(handler RemoteHandler, reader io.Reader, writer io.Writer, logger *log.Logger) (*Remote, error) { 41 | localDir, err := GetLocalDir() 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | repo, err := git.PlainOpen(localDir) 47 | if err == git.ErrWorktreeNotProvided { 48 | repoRoot, _ := path.Split(localDir) 49 | 50 | repo, err = git.PlainOpen(repoRoot) 51 | if err != nil { 52 | return nil, err 53 | } 54 | } 55 | 56 | tracker, err := NewTracker(localDir) 57 | if err != nil { 58 | return nil, fmt.Errorf("fetch: %v", err) 59 | } 60 | 61 | if logger == nil { 62 | logger = log.New(os.Stderr, "", 0) 63 | } 64 | 65 | remote := &Remote{ 66 | reader: reader, 67 | writer: writer, 68 | Logger: logger, 69 | localDir: localDir, 70 | 71 | Repo: repo, 72 | Tracker: tracker, 73 | 74 | Handler: handler, 75 | } 76 | 77 | if err := handler.Initialize(remote); err != nil { 78 | return nil, err 79 | } 80 | 81 | return remote, nil 82 | } 83 | 84 | func (r *Remote) Printf(format string, a ...interface{}) (n int, err error) { 85 | r.Logger.Printf("> "+format, a...) 86 | return fmt.Fprintf(r.writer, format, a...) 87 | } 88 | 89 | func (r *Remote) NewPush() *Push { 90 | return NewPush(r.localDir, r.Tracker, r.Repo) 91 | } 92 | 93 | func (r *Remote) NewFetch() *Fetch { 94 | return NewFetch(r.localDir, r.Tracker, r.Handler.ProvideBlock) 95 | } 96 | 97 | func (r *Remote) Close() error { 98 | return r.Tracker.Close() 99 | } 100 | 101 | func (r *Remote) push(src, dst string, force bool) { 102 | r.todo = append(r.todo, func() (string, error) { 103 | done, err := r.Handler.Push(r, src, dst) 104 | if err != nil { 105 | return "", err 106 | } 107 | 108 | return fmt.Sprintf("ok %s\n", done), nil 109 | }) 110 | } 111 | 112 | func (r *Remote) fetch(sha, ref string) { 113 | r.todo = append(r.todo, func() (string, error) { 114 | fetch := r.NewFetch() 115 | err := fetch.FetchHash(sha) 116 | if err != nil { 117 | return "", fmt.Errorf("command fetch: %v", err) 118 | } 119 | 120 | sha, err := hex.DecodeString(sha) 121 | if err != nil { 122 | return "", fmt.Errorf("fetch: %v", err) 123 | } 124 | 125 | r.Tracker.Set(ref, sha) 126 | return "", nil 127 | }) 128 | } 129 | 130 | func (r *Remote) ProcessCommands() error { 131 | reader := bufio.NewReader(r.reader) 132 | loop: 133 | for { 134 | command, err := reader.ReadString('\n') 135 | if err != nil { 136 | return err 137 | } 138 | 139 | command = strings.Trim(command, "\n") 140 | 141 | r.Logger.Printf("< %s", command) 142 | switch { 143 | case command == "capabilities": 144 | r.Printf("push\n") 145 | r.Printf("fetch\n") 146 | r.Printf("\n") 147 | case strings.HasPrefix(command, "list"): 148 | list, err := r.Handler.List(r, strings.HasPrefix(command, "list for-push")) 149 | if err != nil { 150 | return err 151 | } 152 | for _, e := range list { 153 | r.Printf("%s\n", e) 154 | } 155 | r.Printf("\n") 156 | case strings.HasPrefix(command, "push "): 157 | refs := strings.Split(command[5:], ":") 158 | r.push(refs[0], refs[1], false) //TODO: parse force 159 | case strings.HasPrefix(command, "fetch "): 160 | parts := strings.Split(command, " ") 161 | r.fetch(parts[1], parts[2]) 162 | case command == "": 163 | fallthrough 164 | case command == "\n": 165 | r.Logger.Println("Processing tasks") 166 | for _, task := range r.todo { 167 | resp, err := task() 168 | if err != nil { 169 | return err 170 | } 171 | r.Printf("%s", resp) 172 | } 173 | r.Printf("\n") 174 | r.todo = nil 175 | break loop 176 | default: 177 | return fmt.Errorf("received unknown command %q", command) 178 | } 179 | } 180 | 181 | return r.Handler.Finish(r) 182 | } 183 | -------------------------------------------------------------------------------- /core/tracker.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | 8 | "github.com/dgraph-io/badger" 9 | ) 10 | 11 | //Tracker tracks which hashes are published in IPLD 12 | type Tracker struct { 13 | db *badger.DB 14 | 15 | txn *badger.Txn 16 | } 17 | 18 | func NewTracker(gitPath string) (*Tracker, error) { 19 | ipldDir := path.Join(gitPath, "ipld") 20 | err := os.MkdirAll(ipldDir, 0755) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | opt := badger.DefaultOptions(ipldDir) 26 | 27 | db, err := badger.Open(opt) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return &Tracker{ 33 | db: db, 34 | }, nil 35 | } 36 | 37 | func (t *Tracker) Get(refName string) ([]byte, error) { 38 | txn := t.db.NewTransaction(false) 39 | defer txn.Discard() 40 | 41 | it, err := txn.Get([]byte(refName)) 42 | if err == badger.ErrKeyNotFound { 43 | return nil, nil 44 | } 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return it.ValueCopy(nil) 50 | } 51 | 52 | func (t *Tracker) Set(refName string, hash []byte) error { 53 | txn := t.db.NewTransaction(true) 54 | defer txn.Discard() 55 | 56 | err := txn.Set([]byte(refName), hash) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return txn.Commit() 62 | } 63 | 64 | func (t *Tracker) ListPrefixed(prefix string) (map[string]string, error) { 65 | out := map[string]string{} 66 | 67 | txn := t.db.NewTransaction(false) 68 | defer txn.Discard() 69 | 70 | it := txn.NewIterator(badger.DefaultIteratorOptions) 71 | defer it.Close() 72 | for it.Seek([]byte(prefix)); it.ValidForPrefix([]byte(prefix)); it.Next() { 73 | item := it.Item() 74 | k := item.Key() 75 | v, err := item.ValueCopy(nil) 76 | if err != nil { 77 | return nil, err 78 | } 79 | out[string(k)] = string(v) 80 | } 81 | 82 | return out, nil 83 | } 84 | 85 | func (t *Tracker) AddEntry(hash []byte) error { 86 | if t.txn == nil { 87 | t.txn = t.db.NewTransaction(true) 88 | } 89 | 90 | err := t.txn.Set([]byte(hash), []byte{}) 91 | if err != nil && err.Error() == badger.ErrTxnTooBig.Error() { 92 | if err := t.txn.Commit(); err != nil { 93 | return fmt.Errorf("commit: %s", err) 94 | } 95 | t.txn = t.db.NewTransaction(true) 96 | if err := t.txn.Set([]byte(hash), []byte{}); err != nil { 97 | return err 98 | } 99 | } else if err != nil { 100 | return fmt.Errorf("set: %s", err) 101 | } 102 | 103 | return nil 104 | } 105 | 106 | func (t *Tracker) HasEntry(hash []byte) (bool, error) { 107 | if t.txn == nil { 108 | t.txn = t.db.NewTransaction(true) 109 | } 110 | 111 | _, err := t.txn.Get(hash) 112 | if err == badger.ErrKeyNotFound { 113 | return false, nil 114 | } 115 | if err != nil { 116 | return false, err 117 | } 118 | 119 | return true, nil 120 | } 121 | 122 | func (t *Tracker) Close() error { 123 | if t.txn != nil { 124 | if err := t.txn.Commit(); err != nil { 125 | return err 126 | } 127 | 128 | } 129 | return t.db.Close() 130 | } 131 | -------------------------------------------------------------------------------- /core/util.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "fmt" 7 | "os" 8 | "path" 9 | 10 | cid "github.com/ipfs/go-cid" 11 | mh "github.com/multiformats/go-multihash" 12 | ) 13 | 14 | func compressObject(in []byte) []byte { 15 | var b bytes.Buffer 16 | w := zlib.NewWriter(&b) 17 | w.Write(in) 18 | w.Close() 19 | return b.Bytes() 20 | } 21 | 22 | func GetLocalDir() (string, error) { 23 | localdir := path.Join(os.Getenv("GIT_DIR")) 24 | 25 | if err := os.MkdirAll(localdir, 0755); err != nil { 26 | return "", err 27 | } 28 | return localdir, nil 29 | } 30 | 31 | func CidFromHex(sha string) (cid.Cid, error) { 32 | mhash, err := mh.FromHexString("1114" + sha) 33 | if err != nil { 34 | return cid.Undef, err 35 | } 36 | 37 | return cid.NewCidV1(0x78, mhash), nil 38 | } 39 | 40 | func HexFromCid(cid cid.Cid) (string, error) { 41 | if cid.Type() != 0x78 { 42 | return "", fmt.Errorf("unexpected cid type %d", cid.Type()) 43 | } 44 | 45 | hash := cid.Hash() 46 | // TODO: validate length 47 | return hash.HexString()[4:], nil 48 | } 49 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | git-remote-ipld provides 2 git remotes: 3 | 4 | * git-remote-ipld: allows to import git repositories to IPFS as an IPLD graph. 5 | 6 | * git-remote-ipns: NOT FULLY IMPLEMENTED YET. will allow to track IPLD-backed 7 | remotes using IPNS. 8 | 9 | */ 10 | 11 | package remote 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ipfs-shipyard/git-remote-ipld 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/dgraph-io/badger v1.6.2 7 | github.com/ipfs/go-cid v0.0.2 8 | github.com/ipfs/go-ipfs-api v0.0.1 9 | github.com/ipfs/go-ipld-git v0.0.2 10 | github.com/multiformats/go-multihash v0.0.5 11 | github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce 12 | gopkg.in/src-d/go-git.v4 v4.11.0 13 | ) 14 | 15 | require ( 16 | github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect 17 | github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 // indirect 18 | github.com/cespare/xxhash v1.1.0 // indirect 19 | github.com/dgraph-io/ristretto v0.0.2 // indirect 20 | github.com/dustin/go-humanize v1.0.0 // indirect 21 | github.com/emirpasic/gods v1.9.0 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/protobuf v1.3.1 // indirect 24 | github.com/ipfs/go-block-format v0.0.2 // indirect 25 | github.com/ipfs/go-ipfs-files v0.0.1 // indirect 26 | github.com/ipfs/go-ipfs-util v0.0.1 // indirect 27 | github.com/ipfs/go-ipld-format v0.0.1 // indirect 28 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 29 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e // indirect 30 | github.com/libp2p/go-flow-metrics v0.0.1 // indirect 31 | github.com/libp2p/go-libp2p-crypto v0.0.1 // indirect 32 | github.com/libp2p/go-libp2p-metrics v0.0.1 // indirect 33 | github.com/libp2p/go-libp2p-peer v0.0.1 // indirect 34 | github.com/libp2p/go-libp2p-protocol v0.0.1 // indirect 35 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect 36 | github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5 // indirect 37 | github.com/mitchellh/go-homedir v1.1.0 // indirect 38 | github.com/mr-tron/base58 v1.1.2 // indirect 39 | github.com/multiformats/go-base32 v0.0.3 // indirect 40 | github.com/multiformats/go-multiaddr v0.0.1 // indirect 41 | github.com/multiformats/go-multiaddr-dns v0.0.1 // indirect 42 | github.com/multiformats/go-multiaddr-net v0.0.1 // indirect 43 | github.com/multiformats/go-multibase v0.0.1 // indirect 44 | github.com/pelletier/go-buffruneio v0.2.0 // indirect 45 | github.com/pkg/errors v0.8.1 // indirect 46 | github.com/sergi/go-diff v1.0.0 // indirect 47 | github.com/spaolacci/murmur3 v1.1.0 // indirect 48 | github.com/src-d/gcfg v1.4.0 // indirect 49 | github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c // indirect 50 | github.com/xanzy/ssh-agent v0.2.0 // indirect 51 | golang.org/x/crypto v0.1.0 // indirect 52 | golang.org/x/net v0.7.0 // indirect 53 | golang.org/x/sys v0.5.0 // indirect 54 | gopkg.in/src-d/go-billy.v4 v4.2.1 // indirect 55 | gopkg.in/warnings.v0 v0.1.2 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= 2 | github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= 5 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 6 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 7 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= 8 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 9 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 10 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 11 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 12 | github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 h1:qkOC5Gd33k54tobS36cXdAzJbeHaduLtnLQQwNoIi78= 13 | github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= 14 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 15 | github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 16 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 17 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 18 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 19 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 20 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 21 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 22 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 23 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= 24 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= 25 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 26 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 27 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 28 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 29 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 32 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= 34 | github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= 35 | github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= 36 | github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= 37 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= 38 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 39 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 40 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 41 | github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= 42 | github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 43 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 44 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 45 | github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= 46 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 47 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 48 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 49 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 50 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 51 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 52 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 53 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 54 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 55 | github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= 56 | github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= 57 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 58 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 59 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 60 | github.com/ipfs/go-block-format v0.0.2 h1:qPDvcP19izTjU8rgo6p7gTXZlkMkF5bz5G3fqIsSCPE= 61 | github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= 62 | github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= 63 | github.com/ipfs/go-cid v0.0.2 h1:tuuKaZPU1M6HcejsO3AcYWW8sZ8MTvyxfc4uqB4eFE8= 64 | github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= 65 | github.com/ipfs/go-ipfs-api v0.0.1 h1:4wx4mSgeq5FwMN8LDF7WLwPDKEd+YKjgySrpOJQ2r8o= 66 | github.com/ipfs/go-ipfs-api v0.0.1/go.mod h1:0FhXgCzrLu7qNmdxZvgYqD9jFzJxzz1NAVt3OQ0WOIc= 67 | github.com/ipfs/go-ipfs-files v0.0.1 h1:OroTsI58plHGX70HPLKy6LQhPR3HZJ5ip61fYlo6POM= 68 | github.com/ipfs/go-ipfs-files v0.0.1/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= 69 | github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50= 70 | github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= 71 | github.com/ipfs/go-ipld-format v0.0.1 h1:HCu4eB/Gh+KD/Q0M8u888RFkorTWNIL3da4oc5dwc80= 72 | github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= 73 | github.com/ipfs/go-ipld-git v0.0.2 h1:dn5Quu9lgjkSqkc9CaTsRjzg90kaIitj9wENtigVMH8= 74 | github.com/ipfs/go-ipld-git v0.0.2/go.mod h1:RuvMXa9qtJpDbqngyICCU/d+cmLFXxLsbIclmD0Lcr0= 75 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 76 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 77 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 78 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 79 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 80 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= 81 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 82 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 83 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 84 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 85 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 86 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 87 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 88 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 89 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 90 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 91 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 92 | github.com/libp2p/go-flow-metrics v0.0.1 h1:0gxuFd2GuK7IIP5pKljLwps6TvcuYgvG7Atqi3INF5s= 93 | github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= 94 | github.com/libp2p/go-libp2p-crypto v0.0.1 h1:JNQd8CmoGTohO/akqrH16ewsqZpci2CbgYH/LmYl8gw= 95 | github.com/libp2p/go-libp2p-crypto v0.0.1/go.mod h1:yJkNyDmO341d5wwXxDUGO0LykUVT72ImHNUqh5D/dBE= 96 | github.com/libp2p/go-libp2p-metrics v0.0.1 h1:yumdPC/P2VzINdmcKZd0pciSUCpou+s0lwYCjBbzQZU= 97 | github.com/libp2p/go-libp2p-metrics v0.0.1/go.mod h1:jQJ95SXXA/K1VZi13h52WZMa9ja78zjyy5rspMsC/08= 98 | github.com/libp2p/go-libp2p-peer v0.0.1 h1:0qwAOljzYewINrU+Kndoc+1jAL7vzY/oY2Go4DCGfyY= 99 | github.com/libp2p/go-libp2p-peer v0.0.1/go.mod h1:nXQvOBbwVqoP+T5Y5nCjeH4sP9IX/J0AMzcDUVruVoo= 100 | github.com/libp2p/go-libp2p-protocol v0.0.1 h1:+zkEmZ2yFDi5adpVE3t9dqh/N9TbpFWywowzeEzBbLM= 101 | github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1VZNHYcK8cLgFJLZ4s= 102 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 103 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= 104 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= 105 | github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= 106 | github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5 h1:l16XLUUJ34wIz+RIvLhSwGvLvKyy+W598b135bJN6mg= 107 | github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= 108 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 109 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 110 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 111 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 112 | github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= 113 | github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= 114 | github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 115 | github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= 116 | github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= 117 | github.com/multiformats/go-multiaddr v0.0.1 h1:/QUV3VBMDI6pi6xfiw7lr6xhDWWvQKn9udPn68kLSdY= 118 | github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= 119 | github.com/multiformats/go-multiaddr-dns v0.0.1 h1:jQt9c6tDSdQLIlBo4tXYx7QUHCPjxsB1zXcag/2S7zc= 120 | github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= 121 | github.com/multiformats/go-multiaddr-net v0.0.1 h1:76O59E3FavvHqNg7jvzWzsPSW5JSi/ek0E4eiDVbg9g= 122 | github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= 123 | github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= 124 | github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= 125 | github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= 126 | github.com/multiformats/go-multihash v0.0.5 h1:1wxmCvTXAifAepIMyF39vZinRw5sbqjPs/UIi93+uik= 127 | github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= 128 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 129 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 130 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 131 | github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= 132 | github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= 133 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 134 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 135 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 136 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 137 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 138 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 139 | github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY= 140 | github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= 141 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 142 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 143 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 144 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 145 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 146 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 147 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 148 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 149 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 150 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 151 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 152 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 153 | github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= 154 | github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 155 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 156 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 157 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 158 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 159 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 160 | github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c h1:GGsyl0dZ2jJgVT+VvWBf/cNijrHRhkrTjkmp5wg7li0= 161 | github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs= 162 | github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= 163 | github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= 164 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 165 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 166 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 167 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 168 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 169 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 170 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 171 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 172 | golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 173 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 174 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 175 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 176 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 177 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 178 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= 179 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 180 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 181 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 182 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 183 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 184 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 185 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 186 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 187 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 188 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 189 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 190 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 191 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 192 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 193 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 197 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 198 | golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 199 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 200 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 201 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 202 | golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 203 | golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 204 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 208 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 209 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 210 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 211 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 212 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 213 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 214 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 215 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 216 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 217 | golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= 218 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 219 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 220 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 221 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 222 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 223 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 224 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 225 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 226 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 227 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 228 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 229 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 230 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 231 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 232 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 233 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 234 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 235 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 236 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 237 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 238 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 239 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 240 | gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo= 241 | gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= 242 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= 243 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= 244 | gopkg.in/src-d/go-git.v4 v4.11.0 h1:cJwWgJ0DXifrNrXM6RGN1Y2yR60Rr1zQ9Q5DX5S9qgU= 245 | gopkg.in/src-d/go-git.v4 v4.11.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= 246 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 247 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 248 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 249 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 250 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 251 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 252 | -------------------------------------------------------------------------------- /mock/git/COMMIT_EDITMSG: -------------------------------------------------------------------------------- 1 | English -> Italian 2 | -------------------------------------------------------------------------------- /mock/git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /mock/git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = true 4 | bare = false 5 | logallrefupdates = true 6 | ignorecase = true 7 | precomposeunicode = true 8 | -------------------------------------------------------------------------------- /mock/git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /mock/git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | commitmsg="$(git rev-parse --git-path hooks/commit-msg)" 14 | test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /mock/git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /mock/git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /mock/git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | precommit="$(git rev-parse --git-path hooks/pre-commit)" 13 | test -x "$precommit" && exec "$precommit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /mock/git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config --bool hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | cat <<\EOF 35 | Error: Attempt to add a non-ASCII file name. 36 | 37 | This can cause problems if you want to work with people on other platforms. 38 | 39 | To be portable it is advisable to rename the file. 40 | 41 | If you know what you are doing you can disable this check using: 42 | 43 | git config hooks.allownonascii true 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | exec git diff-index --check --cached $against -- 50 | -------------------------------------------------------------------------------- /mock/git/hooks/pre-push.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # An example hook script to verify what is about to be pushed. Called by "git 4 | # push" after it has checked the remote status, but before anything has been 5 | # pushed. If this script exits with a non-zero status nothing will be pushed. 6 | # 7 | # This hook is called with the following parameters: 8 | # 9 | # $1 -- Name of the remote to which the push is being done 10 | # $2 -- URL to which the push is being done 11 | # 12 | # If pushing without using a named remote those arguments will be equal. 13 | # 14 | # Information about the commits which are being pushed is supplied as lines to 15 | # the standard input in the form: 16 | # 17 | # 18 | # 19 | # This sample shows how to prevent push of commits where the log message starts 20 | # with "WIP" (work in progress). 21 | 22 | remote="$1" 23 | url="$2" 24 | 25 | z40=0000000000000000000000000000000000000000 26 | 27 | while read local_ref local_sha remote_ref remote_sha 28 | do 29 | if [ "$local_sha" = $z40 ] 30 | then 31 | # Handle delete 32 | : 33 | else 34 | if [ "$remote_sha" = $z40 ] 35 | then 36 | # New branch, examine all commits 37 | range="$local_sha" 38 | else 39 | # Update to existing branch, examine new commits 40 | range="$remote_sha..$local_sha" 41 | fi 42 | 43 | # Check for WIP commit 44 | commit=`git rev-list -n 1 --grep '^WIP' "$range"` 45 | if [ -n "$commit" ] 46 | then 47 | echo >&2 "Found WIP commit in $local_ref, not pushing" 48 | exit 1 49 | fi 50 | fi 51 | done 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /mock/git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /mock/git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /mock/git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /mock/git/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/index -------------------------------------------------------------------------------- /mock/git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /mock/git/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945658 -0500 commit (initial): Say hello 2 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945683 -0500 checkout: moving from master to french 3 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 31f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00 Dirk McCormick 1517945712 -0500 commit: English -> French 4 | 31f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00 162429cc0dac923dff140ec29247f42a8e362419 Dirk McCormick 1517945735 -0500 commit: goodbye.txt 5 | 162429cc0dac923dff140ec29247f42a8e362419 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945739 -0500 checkout: moving from french to master 6 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945747 -0500 checkout: moving from master to italian 7 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 78a77abd233c24d8e6a0d0d040c79ae569fc7a19 Dirk McCormick 1517945768 -0500 commit: English -> Italian 8 | 78a77abd233c24d8e6a0d0d040c79ae569fc7a19 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945774 -0500 checkout: moving from italian to master 9 | -------------------------------------------------------------------------------- /mock/git/logs/refs/heads/french: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945683 -0500 branch: Created from HEAD 2 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 31f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00 Dirk McCormick 1517945712 -0500 commit: English -> French 3 | 31f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00 162429cc0dac923dff140ec29247f42a8e362419 Dirk McCormick 1517945735 -0500 commit: goodbye.txt 4 | -------------------------------------------------------------------------------- /mock/git/logs/refs/heads/italian: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945747 -0500 branch: Created from HEAD 2 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 78a77abd233c24d8e6a0d0d040c79ae569fc7a19 Dirk McCormick 1517945768 -0500 commit: English -> Italian 3 | -------------------------------------------------------------------------------- /mock/git/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 Dirk McCormick 1517945658 -0500 commit (initial): Say hello 2 | -------------------------------------------------------------------------------- /mock/git/objects/04/47b05c9be04cd29f437f2ec98af9b5a6a17156: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/04/47b05c9be04cd29f437f2ec98af9b5a6a17156 -------------------------------------------------------------------------------- /mock/git/objects/16/2429cc0dac923dff140ec29247f42a8e362419: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/16/2429cc0dac923dff140ec29247f42a8e362419 -------------------------------------------------------------------------------- /mock/git/objects/31/f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/31/f087b9bf39d5bcbba5d4e80b2b4ff19a71dc00 -------------------------------------------------------------------------------- /mock/git/objects/45/306d9e3a93e015de956ec7be2b3fb377d3ec85: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/45/306d9e3a93e015de956ec7be2b3fb377d3ec85 -------------------------------------------------------------------------------- /mock/git/objects/58/1caa0fe56cf01dc028cc0b089d364993e046b6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/58/1caa0fe56cf01dc028cc0b089d364993e046b6 -------------------------------------------------------------------------------- /mock/git/objects/78/a77abd233c24d8e6a0d0d040c79ae569fc7a19: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/78/a77abd233c24d8e6a0d0d040c79ae569fc7a19 -------------------------------------------------------------------------------- /mock/git/objects/95/3ee2f1c6a92eeff7604e9a5e6c93b1d28615c8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/95/3ee2f1c6a92eeff7604e9a5e6c93b1d28615c8 -------------------------------------------------------------------------------- /mock/git/objects/98/0a0d5f19a64b4b30a87d4206aade58726b60e3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/98/0a0d5f19a64b4b30a87d4206aade58726b60e3 -------------------------------------------------------------------------------- /mock/git/objects/98/991eb8d6cb267d0ccc296c29b109d3d062da76: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/98/991eb8d6cb267d0ccc296c29b109d3d062da76 -------------------------------------------------------------------------------- /mock/git/objects/a1/0a342f7ead6bcea5a7564b2f3cee749063ad60: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/a1/0a342f7ead6bcea5a7564b2f3cee749063ad60 -------------------------------------------------------------------------------- /mock/git/objects/d5/b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/d5/b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 -------------------------------------------------------------------------------- /mock/git/objects/ee/bcc8ed1a3d42b62a8996e54d39e2d831ab5570: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/git-remote-ipld/c2e5241335dea54026e3599b31c36e84125bcc6f/mock/git/objects/ee/bcc8ed1a3d42b62a8996e54d39e2d831ab5570 -------------------------------------------------------------------------------- /mock/git/refs/heads/french: -------------------------------------------------------------------------------- 1 | 162429cc0dac923dff140ec29247f42a8e362419 2 | -------------------------------------------------------------------------------- /mock/git/refs/heads/italian: -------------------------------------------------------------------------------- 1 | 78a77abd233c24d8e6a0d0d040c79ae569fc7a19 2 | -------------------------------------------------------------------------------- /mock/git/refs/heads/master: -------------------------------------------------------------------------------- 1 | d5b0d08c180fd7a9bf4f684a37e60ceeb4d25ec8 2 | -------------------------------------------------------------------------------- /util/compare.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "math" 10 | "os" 11 | "path/filepath" 12 | "sort" 13 | ) 14 | 15 | type byName []os.FileInfo 16 | 17 | func (n byName) Len() int { return len(n) } 18 | func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } 19 | func (n byName) Less(i, j int) bool { return n[i].Name() < n[j].Name() } 20 | 21 | func CompareDirs(srcPath, dstPath string, ignore []string) error { 22 | ignoreSet := map[string]bool{} 23 | for _, val := range ignore { 24 | ignoreSet[val] = true 25 | } 26 | 27 | filterEntries := func(a []os.FileInfo) []os.FileInfo { 28 | count := 0 29 | for i := 0; i < len(a)-count; i++ { 30 | if ignoreSet[a[i].Name()] { 31 | a[i] = a[len(a)-1-count] 32 | count++ 33 | } 34 | } 35 | return a[:len(a)-count] 36 | } 37 | 38 | srcEntries, err := ioutil.ReadDir(srcPath) 39 | if err != nil { 40 | return err 41 | } 42 | dstEntries, err := ioutil.ReadDir(dstPath) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | srcEntries = filterEntries(srcEntries) 48 | dstEntries = filterEntries(dstEntries) 49 | sort.Sort(byName(srcEntries)) 50 | sort.Sort(byName(dstEntries)) 51 | 52 | for i := 0; float64(i) < math.Max(float64(len(srcEntries)), float64(len(dstEntries))); i++ { 53 | if i >= len(srcEntries) { 54 | dstSubPath := filepath.Join(dstPath, dstEntries[i].Name()) 55 | return fmt.Errorf("file %s in destination directory does not exist in source directory %s", dstSubPath, srcPath) 56 | } 57 | if i >= len(dstEntries) { 58 | srcSubPath := filepath.Join(srcPath, srcEntries[i].Name()) 59 | return fmt.Errorf("file %s in source directory does not exist in destination directory %s", srcSubPath, dstPath) 60 | } 61 | if srcEntries[i].Name() < dstEntries[i].Name() { 62 | srcSubPath := filepath.Join(srcPath, srcEntries[i].Name()) 63 | return fmt.Errorf("file %s in source directory does not exist in destination directory %s", srcSubPath, dstPath) 64 | } else if srcEntries[i].Name() > dstEntries[i].Name() { 65 | dstSubPath := filepath.Join(dstPath, dstEntries[i].Name()) 66 | return fmt.Errorf("file %s in destination directory does not exist in source directory %s", dstSubPath, srcPath) 67 | } 68 | } 69 | 70 | for _, entry := range srcEntries { 71 | srcSubPath := filepath.Join(srcPath, entry.Name()) 72 | dstSubPath := filepath.Join(dstPath, entry.Name()) 73 | 74 | if entry.IsDir() { 75 | err = CompareDirs(srcSubPath, dstSubPath, ignore) 76 | if err != nil { 77 | return err 78 | } 79 | // Skip symlinks. 80 | } else if entry.Mode()&os.ModeSymlink == 0 { 81 | err = CompareFiles(srcSubPath, dstSubPath) 82 | if err != nil { 83 | return err 84 | } 85 | } 86 | } 87 | return nil 88 | } 89 | 90 | const chunkSize = 64000 91 | 92 | func CompareFiles(file1, file2 string) error { 93 | f1, err := os.Open(file1) 94 | if err != nil { 95 | return err 96 | } 97 | defer f1.Close() 98 | 99 | f2, err := os.Open(file2) 100 | if err != nil { 101 | return err 102 | } 103 | defer f2.Close() 104 | 105 | for { 106 | b1 := make([]byte, chunkSize) 107 | _, err1 := f1.Read(b1) 108 | 109 | b2 := make([]byte, chunkSize) 110 | _, err2 := f2.Read(b2) 111 | 112 | if err1 != nil || err2 != nil { 113 | if err1 == io.EOF && err2 == io.EOF { 114 | return nil 115 | } else if err1 == io.EOF || err2 == io.EOF { 116 | f1.Close() 117 | f2.Close() 118 | return CompareZlib(file1, file2) 119 | } else { 120 | return err1 121 | } 122 | } 123 | 124 | if !bytes.Equal(b1, b2) { 125 | f1.Close() 126 | f2.Close() 127 | return CompareZlib(file1, file2) 128 | } 129 | } 130 | } 131 | 132 | func CompareZlib(file1, file2 string) error { 133 | z1, err := os.Open(file1) 134 | if err != nil { 135 | return err 136 | } 137 | defer z1.Close() 138 | 139 | z2, err := os.Open(file2) 140 | if err != nil { 141 | return err 142 | } 143 | defer z2.Close() 144 | 145 | f1, err := zlib.NewReader(z1) 146 | if err != nil { 147 | return err 148 | } 149 | defer f1.Close() 150 | 151 | f2, err := zlib.NewReader(z2) 152 | if err != nil { 153 | return err 154 | } 155 | defer f2.Close() 156 | 157 | for { 158 | b1 := make([]byte, chunkSize) 159 | _, err1 := f1.Read(b1) 160 | 161 | b2 := make([]byte, chunkSize) 162 | _, err2 := f2.Read(b2) 163 | 164 | if err1 != nil || err2 != nil { 165 | if err1 == io.EOF && err2 == io.EOF { 166 | return nil 167 | } else if err1 == io.EOF || err2 == io.EOF { 168 | return fmt.Errorf("file %s != %s", file1, file2) 169 | } else { 170 | return err1 171 | } 172 | } 173 | 174 | if !bytes.Equal(b1, b2) { 175 | return fmt.Errorf("file %s != %s", file1, file2) 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /util/copy.go: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | * 3 | * Copyright (c) 2017 Roland Singer [roland.singer@desertbit.com] 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | package util 24 | 25 | import ( 26 | "fmt" 27 | "io" 28 | "io/ioutil" 29 | "os" 30 | "path/filepath" 31 | ) 32 | 33 | // CopyFile copies the contents of the file named src to the file named 34 | // by dst. The file will be created if it does not already exist. If the 35 | // destination file exists, all it's contents will be replaced by the contents 36 | // of the source file. The file mode will be copied from the source and 37 | // the copied data is synced/flushed to stable storage. 38 | func CopyFile(src, dst string) (err error) { 39 | in, err := os.Open(src) 40 | if err != nil { 41 | return 42 | } 43 | defer in.Close() 44 | 45 | out, err := os.Create(dst) 46 | if err != nil { 47 | return 48 | } 49 | defer func() { 50 | if e := out.Close(); e != nil { 51 | err = e 52 | } 53 | }() 54 | 55 | _, err = io.Copy(out, in) 56 | if err != nil { 57 | return 58 | } 59 | 60 | err = out.Sync() 61 | if err != nil { 62 | return 63 | } 64 | 65 | si, err := os.Stat(src) 66 | if err != nil { 67 | return 68 | } 69 | err = os.Chmod(dst, si.Mode()) 70 | if err != nil { 71 | return 72 | } 73 | 74 | return 75 | } 76 | 77 | // CopyDir recursively copies a directory tree, attempting to preserve permissions. 78 | // Source directory must exist, destination directory must *not* exist. 79 | // Symlinks are ignored and skipped. 80 | func CopyDir(src string, dst string) (err error) { 81 | src = filepath.Clean(src) 82 | dst = filepath.Clean(dst) 83 | 84 | si, err := os.Stat(src) 85 | if err != nil { 86 | return err 87 | } 88 | if !si.IsDir() { 89 | return fmt.Errorf("source is not a directory") 90 | } 91 | 92 | _, err = os.Stat(dst) 93 | if err != nil && !os.IsNotExist(err) { 94 | return 95 | } 96 | if err == nil { 97 | return fmt.Errorf("destination already exists") 98 | } 99 | 100 | err = os.MkdirAll(dst, si.Mode()) 101 | if err != nil { 102 | return 103 | } 104 | 105 | entries, err := ioutil.ReadDir(src) 106 | if err != nil { 107 | return 108 | } 109 | 110 | for _, entry := range entries { 111 | srcPath := filepath.Join(src, entry.Name()) 112 | dstPath := filepath.Join(dst, entry.Name()) 113 | 114 | if entry.IsDir() { 115 | err = CopyDir(srcPath, dstPath) 116 | if err != nil { 117 | return 118 | } 119 | } else { 120 | // Skip symlinks. 121 | if entry.Mode()&os.ModeSymlink != 0 { 122 | continue 123 | } 124 | 125 | err = CopyFile(srcPath, dstPath) 126 | if err != nil { 127 | return 128 | } 129 | } 130 | } 131 | 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "" 3 | } 4 | --------------------------------------------------------------------------------