From b2651591b820c8cfba2c140b3a6336673f0084a0 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 13:04:20 -0700 Subject: [PATCH 01/79] started init --- cli/init.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 cli/init.go diff --git a/cli/init.go b/cli/init.go new file mode 100644 index 0000000..6aa11c4 --- /dev/null +++ b/cli/init.go @@ -0,0 +1,45 @@ +package cli + +import ( + "os" + "path/filepath" + + "github.com/codegangsta/cli" +) + +const ( + // SpreadDirectory is the name of the directory that holds a Spread repository. + SpreadDirectory = ".spread" +) + +// Init sets up a Spread repository for versioning. +func (s SpreadCli) Init() *cli.Command { + return &cli.Command{ + Name: "init", + Usage: "spread init ", + Description: "Create a new spread repository in the given directory. If none is given, the working directory will be used.", + Action: func(c *cli.Context) { + target := SpreadDirectory + // Check if path is specified + if len(c.Args().First()) != 0 { + target = c.Args().First() + } + + // Get absolute path to directory + target, err := filepath.Abs(target) + if err != nil { + s.fatalf("Could not resolve '%s': %v", target, err) + } + + // Create .spread directory in target directory + if err = os.MkdirAll(target, 0755); os.IsNotExist(err) { + s.fatalf("Could not initialize repo: '%s' already exists", target) + } else if err != nil { + s.fatalf("Could not create repo directory: %v", err) + } + + // Create bare Git repository in .spread directory with the directory name "git" + // Create .gitignore file in directory ignoring Git repository + }, + } +} From fef8543cf483ae8d6a0732e123f8750dabfba96d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 13:46:44 -0700 Subject: [PATCH 02/79] working 'spread init' --- cli/cli.go | 8 ++++++++ cli/init.go | 27 +++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index f43d843..18fb555 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -7,6 +7,14 @@ import ( "strings" ) +const ( + // SpreadDirectory is the name of the directory that holds a Spread repository. + SpreadDirectory = ".spread" + + // GitDirectory is the name of the directory holding the bare Git repository within the SpreadDirectory. + GitDirectory = "git" +) + // SpreadCli is the spread command line client. type SpreadCli struct { // input stream (ie. stdin) diff --git a/cli/init.go b/cli/init.go index 6aa11c4..a704192 100644 --- a/cli/init.go +++ b/cli/init.go @@ -1,15 +1,13 @@ package cli import ( + "fmt" + "io/ioutil" "os" "path/filepath" "github.com/codegangsta/cli" -) - -const ( - // SpreadDirectory is the name of the directory that holds a Spread repository. - SpreadDirectory = ".spread" + git "gopkg.in/libgit2/git2go.v23" ) // Init sets up a Spread repository for versioning. @@ -31,15 +29,28 @@ func (s SpreadCli) Init() *cli.Command { s.fatalf("Could not resolve '%s': %v", target, err) } - // Create .spread directory in target directory - if err = os.MkdirAll(target, 0755); os.IsNotExist(err) { + // Check if directory exists + if _, err = os.Stat(target); err == nil { s.fatalf("Could not initialize repo: '%s' already exists", target) - } else if err != nil { + } else if !os.IsNotExist(err) { + s.fatalf("Could not stat repo directory: %v", err) + } + + // Create .spread directory in target directory + if err = os.MkdirAll(target, 0755); err != nil { s.fatalf("Could not create repo directory: %v", err) } // Create bare Git repository in .spread directory with the directory name "git" + gitDir := filepath.Join(target, GitDirectory) + if _, err = git.InitRepository(gitDir, true); err != nil { + s.fatalf("Could not create Object repository: %v", err) + } + // Create .gitignore file in directory ignoring Git repository + ignoreName := filepath.Join(SpreadDirectory, ".gitignore") + ignoreData := fmt.Sprintf("/%s", GitDirectory) + ioutil.WriteFile(ignoreName, []byte(ignoreData), 0755) }, } } From dc49f50a6ca0c23215141082f1befc663a0536a6 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 16:18:11 -0700 Subject: [PATCH 03/79] install libgit2 --- .travis.yml | 1 + Godeps/Godeps.json | 6 +- Makefile | 13 + hack/build-libgit2.sh | 13 + .../gopkg.in/libgit2/git2go.v23/.travis.yml | 17 + vendor/gopkg.in/libgit2/git2go.v23/LICENSE | 21 + vendor/gopkg.in/libgit2/git2go.v23/Makefile | 8 + vendor/gopkg.in/libgit2/git2go.v23/README.md | 67 ++ vendor/gopkg.in/libgit2/git2go.v23/blame.go | 157 ++++ vendor/gopkg.in/libgit2/git2go.v23/blob.go | 104 +++ vendor/gopkg.in/libgit2/git2go.v23/branch.go | 245 ++++++ .../gopkg.in/libgit2/git2go.v23/checkout.go | 164 ++++ .../gopkg.in/libgit2/git2go.v23/cherrypick.go | 73 ++ vendor/gopkg.in/libgit2/git2go.v23/clone.go | 110 +++ vendor/gopkg.in/libgit2/git2go.v23/commit.go | 110 +++ vendor/gopkg.in/libgit2/git2go.v23/config.go | 414 ++++++++++ .../libgit2/git2go.v23/credentials.go | 73 ++ .../gopkg.in/libgit2/git2go.v23/describe.go | 222 ++++++ vendor/gopkg.in/libgit2/git2go.v23/diff.go | 720 +++++++++++++++++ vendor/gopkg.in/libgit2/git2go.v23/git.go | 295 +++++++ vendor/gopkg.in/libgit2/git2go.v23/graph.go | 36 + vendor/gopkg.in/libgit2/git2go.v23/handles.go | 57 ++ vendor/gopkg.in/libgit2/git2go.v23/index.go | 471 +++++++++++ vendor/gopkg.in/libgit2/git2go.v23/merge.go | 417 ++++++++++ vendor/gopkg.in/libgit2/git2go.v23/note.go | 220 ++++++ vendor/gopkg.in/libgit2/git2go.v23/object.go | 138 ++++ vendor/gopkg.in/libgit2/git2go.v23/odb.go | 316 ++++++++ .../libgit2/git2go.v23/packbuilder.go | 153 ++++ vendor/gopkg.in/libgit2/git2go.v23/patch.go | 87 ++ vendor/gopkg.in/libgit2/git2go.v23/refdb.go | 56 ++ .../gopkg.in/libgit2/git2go.v23/reference.go | 451 +++++++++++ vendor/gopkg.in/libgit2/git2go.v23/remote.go | 747 ++++++++++++++++++ .../gopkg.in/libgit2/git2go.v23/repository.go | 456 +++++++++++ vendor/gopkg.in/libgit2/git2go.v23/reset.go | 26 + .../gopkg.in/libgit2/git2go.v23/revparse.go | 113 +++ .../gopkg.in/libgit2/git2go.v23/settings.go | 102 +++ .../gopkg.in/libgit2/git2go.v23/signature.go | 73 ++ vendor/gopkg.in/libgit2/git2go.v23/status.go | 179 +++++ .../gopkg.in/libgit2/git2go.v23/submodule.go | 342 ++++++++ vendor/gopkg.in/libgit2/git2go.v23/tag.go | 211 +++++ vendor/gopkg.in/libgit2/git2go.v23/tree.go | 195 +++++ vendor/gopkg.in/libgit2/git2go.v23/walk.go | 209 +++++ vendor/gopkg.in/libgit2/git2go.v23/wrapper.c | 167 ++++ 43 files changed, 8054 insertions(+), 1 deletion(-) create mode 100755 hack/build-libgit2.sh create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/.travis.yml create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/LICENSE create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/Makefile create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/README.md create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/blame.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/blob.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/branch.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/checkout.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/cherrypick.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/clone.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/commit.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/config.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/credentials.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/describe.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/diff.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/git.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/graph.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/handles.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/index.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/merge.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/note.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/object.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/odb.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/packbuilder.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/patch.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/refdb.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/reference.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/remote.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/repository.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/reset.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/revparse.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/settings.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/signature.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/status.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/submodule.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/tag.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/tree.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/walk.go create mode 100644 vendor/gopkg.in/libgit2/git2go.v23/wrapper.c diff --git a/.travis.yml b/.travis.yml index 8c5af7c..6b8af6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ services: install: # install build dependencies - make deps + - make build-libgit2 before_script: - make validate diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b81ee99..7c46484 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,7 @@ { "ImportPath": "rsprd.com/spread", "GoVersion": "go1.6", - "GodepVersion": "v60", + "GodepVersion": "v62", "Packages": [ "./pkg/...", "./cli/...", @@ -308,6 +308,10 @@ "ImportPath": "golang.org/x/net/context", "Rev": "c2528b2dd8352441850638a8bb678c2ad056fd3e" }, + { + "ImportPath": "gopkg.in/libgit2/git2go.v23", + "Rev": "fa644d2fc9efa3baee93b525212d76dfa17a5db5" + }, { "ImportPath": "gopkg.in/yaml.v2", "Rev": "d466437aa4adc35830964cffc5b5f262c63ddcb4" diff --git a/Makefile b/Makefile index 62cf74b..f526e0b 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ ifndef SPREAD_VERSION SPREAD_VERSION := v0.0.0 endif +LIBGIT2_VERSION ?= v0.23.4 + GOX_OS ?= linux darwin windows GOX_ARCH ?= amd64 @@ -30,6 +32,7 @@ GOX_FLAGS ?= -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" -os="${GOX_OS}" -arch="$ STATIC_LDFLAGS ?= -extldflags "-static" --s -w GITLAB_CONTEXT ?= ./build/gitlab +LIBGIT2_URL ?= https://github.com/libgit2/libgit2/archive/$(LIBGIT2_VERSION).tar.gz # image data ORG ?= redspreadapps @@ -83,6 +86,16 @@ build-gitlab: build/spread-linux-static cp ./build/spread-linux-static $(GITLAB_CONTEXT) $(DOCKER) build $(DOCKER_OPTS) -t $(GITLAB_IMAGE_NAME) $(GITLAB_CONTEXT) +build-libgit2: vendor/libgit2/build/libgit2.a +vendor/libgit2/build/libgit2.a: vendor/libgit2 + ./hack/build-libgit2.sh + +vendor/libgit2: vendor/libgit2.tar.gz + mkdir -p $@ + tar -zxf $< -C $@ --strip-components=1 +vendor/libgit2.tar.gz: + curl -L -o $@ $(LIBGIT2_URL) + .PHONY: vet vet: $(GO) vet $(PKGS) diff --git a/hack/build-libgit2.sh b/hack/build-libgit2.sh new file mode 100755 index 0000000..9a07a00 --- /dev/null +++ b/hack/build-libgit2.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -ex + +VENDORED_PATH=vendor/libgit2 + +cd $VENDORED_PATH && +mkdir -p install/lib && +mkdir -p build && +cd build && +cmake . && +make && +sudo make install diff --git a/vendor/gopkg.in/libgit2/git2go.v23/.travis.yml b/vendor/gopkg.in/libgit2/git2go.v23/.travis.yml new file mode 100644 index 0000000..f796389 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/.travis.yml @@ -0,0 +1,17 @@ +language: go + +sudo: required + +install: ./script/install-libgit2.sh + +go: + - 1.1 + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - tip + +matrix: + allow_failures: + - go: tip diff --git a/vendor/gopkg.in/libgit2/git2go.v23/LICENSE b/vendor/gopkg.in/libgit2/git2go.v23/LICENSE new file mode 100644 index 0000000..46a7407 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013 The git2go contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/gopkg.in/libgit2/git2go.v23/Makefile b/vendor/gopkg.in/libgit2/git2go.v23/Makefile new file mode 100644 index 0000000..9c42283 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/Makefile @@ -0,0 +1,8 @@ +default: test + +test: + go run script/check-MakeGitError-thread-lock.go + go test ./... + +install: + go install ./... diff --git a/vendor/gopkg.in/libgit2/git2go.v23/README.md b/vendor/gopkg.in/libgit2/git2go.v23/README.md new file mode 100644 index 0000000..315032f --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/README.md @@ -0,0 +1,67 @@ +git2go +====== +[![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=master)](https://travis-ci.org/libgit2/git2go) + + +Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follows the latest libgit2 release. The versioned branches indicate which libgit2 version they work against. + +Installing +---------- + +This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manager and then install git2go as usual. + +Otherwise (`next` which tracks an unstable version), we need to build libgit2 as well. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. + +### Stable version + +git2go has `master` which tracks the latest release of libgit2, and versioned branches which indicate which version of libgit2 they work against. Install the development package on your system via your favourite package manager or from source and you can use a service like gopkg.in to use the appropriate version. For the libgit2 v0.22 case, you can use + + import "gopkg.in/libgit2/git2go.v22" + +to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library. You can use + + import "github.com/libgit2/git2go" + +to use the version which works against the latest release. + +### From `next` + +The `next` branch follows libgit2's master branch, which means there is no stable API or ABI to link against. git2go can statically link against a vendored version of libgit2. + +Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$GOPATH/src/github.com/libgit2/git2go` directory. From there, we need to build the C code and put it into the resulting go binary. + + git checkout next + git submodule update --init # get libgit2 + make install + +will compile libgit2. Run `go install` so that it's statically linked to the git2go package. + +Parallelism and network operations +---------------------------------- + +libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information. + +Running the tests +----------------- + +For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper + + make test + +Alternatively, if you want to pass arguments to `go test`, you can use the script that sets it all up + + ./script/with-static.sh go test -v + +which will run the specified arguments with the correct environment variables. + +License +------- + +M to the I to the T. See the LICENSE file if you've never seen a MIT license before. + +Authors +------- + +- Carlos Martín (@carlosmn) +- Vicent Martí (@vmg) + diff --git a/vendor/gopkg.in/libgit2/git2go.v23/blame.go b/vendor/gopkg.in/libgit2/git2go.v23/blame.go new file mode 100644 index 0000000..c24c934 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/blame.go @@ -0,0 +1,157 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type BlameOptions struct { + Flags BlameOptionsFlag + MinMatchCharacters uint16 + NewestCommit *Oid + OldestCommit *Oid + MinLine uint32 + MaxLine uint32 +} + +func DefaultBlameOptions() (BlameOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + opts := C.git_blame_options{} + ecode := C.git_blame_init_options(&opts, C.GIT_BLAME_OPTIONS_VERSION) + if ecode < 0 { + return BlameOptions{}, MakeGitError(ecode) + } + + return BlameOptions{ + Flags: BlameOptionsFlag(opts.flags), + MinMatchCharacters: uint16(opts.min_match_characters), + NewestCommit: newOidFromC(&opts.newest_commit), + OldestCommit: newOidFromC(&opts.oldest_commit), + MinLine: uint32(opts.min_line), + MaxLine: uint32(opts.max_line), + }, nil +} + +type BlameOptionsFlag uint32 + +const ( + BlameNormal BlameOptionsFlag = C.GIT_BLAME_NORMAL + BlameTrackCopiesSameFile BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_FILE + BlameTrackCopiesSameCommitMoves BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES + BlameTrackCopiesSameCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES + BlameTrackCopiesAnyCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES + BlameFirstParent BlameOptionsFlag = C.GIT_BLAME_FIRST_PARENT +) + +func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) { + var blamePtr *C.git_blame + + var copts *C.git_blame_options + if opts != nil { + copts = &C.git_blame_options{ + version: C.GIT_BLAME_OPTIONS_VERSION, + flags: C.uint32_t(opts.Flags), + min_match_characters: C.uint16_t(opts.MinMatchCharacters), + min_line: C.uint32_t(opts.MinLine), + max_line: C.uint32_t(opts.MaxLine), + } + if opts.NewestCommit != nil { + copts.newest_commit = *opts.NewestCommit.toC() + } + if opts.OldestCommit != nil { + copts.oldest_commit = *opts.OldestCommit.toC() + } + } + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_blame_file(&blamePtr, v.ptr, cpath, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newBlameFromC(blamePtr), nil +} + +type Blame struct { + ptr *C.git_blame +} + +func (blame *Blame) HunkCount() int { + return int(C.git_blame_get_hunk_count(blame.ptr)) +} + +func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) { + ptr := C.git_blame_get_hunk_byindex(blame.ptr, C.uint32_t(index)) + if ptr == nil { + return BlameHunk{}, ErrInvalid + } + return blameHunkFromC(ptr), nil +} + +func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) { + ptr := C.git_blame_get_hunk_byline(blame.ptr, C.uint32_t(lineno)) + if ptr == nil { + return BlameHunk{}, ErrInvalid + } + return blameHunkFromC(ptr), nil +} + +func newBlameFromC(ptr *C.git_blame) *Blame { + if ptr == nil { + return nil + } + + blame := &Blame{ + ptr: ptr, + } + + runtime.SetFinalizer(blame, (*Blame).Free) + return blame +} + +func (blame *Blame) Free() error { + if blame.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(blame, nil) + C.git_blame_free(blame.ptr) + blame.ptr = nil + return nil +} + +type BlameHunk struct { + LinesInHunk uint16 + FinalCommitId *Oid + FinalStartLineNumber uint16 + FinalSignature *Signature + OrigCommitId *Oid + OrigPath string + OrigStartLineNumber uint16 + OrigSignature *Signature + Boundary bool +} + +func blameHunkFromC(hunk *C.git_blame_hunk) BlameHunk { + return BlameHunk{ + LinesInHunk: uint16(hunk.lines_in_hunk), + FinalCommitId: newOidFromC(&hunk.final_commit_id), + FinalStartLineNumber: uint16(hunk.final_start_line_number), + FinalSignature: newSignatureFromC(hunk.final_signature), + OrigCommitId: newOidFromC(&hunk.orig_commit_id), + OrigPath: C.GoString(hunk.orig_path), + OrigStartLineNumber: uint16(hunk.orig_start_line_number), + OrigSignature: newSignatureFromC(hunk.orig_signature), + Boundary: hunk.boundary == 1, + } +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/blob.go b/vendor/gopkg.in/libgit2/git2go.v23/blob.go new file mode 100644 index 0000000..382bb9e --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/blob.go @@ -0,0 +1,104 @@ +package git + +/* +#include +#include + +extern int _go_git_blob_create_fromchunks(git_oid *id, + git_repository *repo, + const char *hintpath, + void *payload); + +*/ +import "C" +import ( + "io" + "runtime" + "unsafe" +) + +type Blob struct { + gitObject + cast_ptr *C.git_blob +} + +func (v *Blob) Size() int64 { + return int64(C.git_blob_rawsize(v.cast_ptr)) +} + +func (v *Blob) Contents() []byte { + size := C.int(C.git_blob_rawsize(v.cast_ptr)) + buffer := unsafe.Pointer(C.git_blob_rawcontent(v.cast_ptr)) + return C.GoBytes(buffer, size) +} + +func (repo *Repository) CreateBlobFromBuffer(data []byte) (*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var id C.git_oid + var ptr unsafe.Pointer + + if len(data) > 0 { + ptr = unsafe.Pointer(&data[0]) + } else { + ptr = unsafe.Pointer(nil) + } + + ecode := C.git_blob_create_frombuffer(&id, repo.ptr, ptr, C.size_t(len(data))) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newOidFromC(&id), nil +} + +type BlobChunkCallback func(maxLen int) ([]byte, error) + +type BlobCallbackData struct { + Callback BlobChunkCallback + Error error +} + +//export blobChunkCb +func blobChunkCb(buffer *C.char, maxLen C.size_t, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*BlobCallbackData) + if !ok { + panic("could not retrieve blob callback data") + } + + goBuf, err := data.Callback(int(maxLen)) + if err == io.EOF { + return 0 + } else if err != nil { + data.Error = err + return -1 + } + C.memcpy(unsafe.Pointer(buffer), unsafe.Pointer(&goBuf[0]), C.size_t(len(goBuf))) + return len(goBuf) +} + +func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var chintPath *C.char = nil + if len(hintPath) > 0 { + chintPath = C.CString(hintPath) + defer C.free(unsafe.Pointer(chintPath)) + } + oid := C.git_oid{} + + payload := &BlobCallbackData{Callback: callback} + handle := pointerHandles.Track(payload) + defer pointerHandles.Untrack(handle) + + ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle) + if payload.Error != nil { + return nil, payload.Error + } + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newOidFromC(&oid), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/branch.go b/vendor/gopkg.in/libgit2/git2go.v23/branch.go new file mode 100644 index 0000000..df72dba --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/branch.go @@ -0,0 +1,245 @@ +package git + +/* +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +type BranchType uint + +const ( + BranchLocal BranchType = C.GIT_BRANCH_LOCAL + BranchRemote BranchType = C.GIT_BRANCH_REMOTE +) + +type Branch struct { + *Reference +} + +func (r *Reference) Branch() *Branch { + return &Branch{Reference: r} +} + +type BranchIterator struct { + ptr *C.git_branch_iterator + repo *Repository +} + +type BranchIteratorFunc func(*Branch, BranchType) error + +func newBranchIteratorFromC(repo *Repository, ptr *C.git_branch_iterator) *BranchIterator { + i := &BranchIterator{repo: repo, ptr: ptr} + runtime.SetFinalizer(i, (*BranchIterator).Free) + return i +} + +func (i *BranchIterator) Next() (*Branch, BranchType, error) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var refPtr *C.git_reference + var refType C.git_branch_t + + ecode := C.git_branch_next(&refPtr, &refType, i.ptr) + + if ecode < 0 { + return nil, BranchLocal, MakeGitError(ecode) + } + + branch := newReferenceFromC(refPtr, i.repo).Branch() + + return branch, BranchType(refType), nil +} + +func (i *BranchIterator) Free() { + runtime.SetFinalizer(i, nil) + C.git_branch_iterator_free(i.ptr) +} + +func (i *BranchIterator) ForEach(f BranchIteratorFunc) error { + b, t, err := i.Next() + + for err == nil { + err = f(b, t) + if err == nil { + b, t, err = i.Next() + } + } + + return err +} + +func (repo *Repository) NewBranchIterator(flags BranchType) (*BranchIterator, error) { + refType := C.git_branch_t(flags) + var ptr *C.git_branch_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_branch_iterator_new(&ptr, repo.ptr, refType) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newBranchIteratorFromC(repo, ptr), nil +} + +func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool) (*Branch, error) { + + var ptr *C.git_reference + cBranchName := C.CString(branchName) + defer C.free(unsafe.Pointer(cBranchName)) + cForce := cbool(force) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_create(&ptr, repo.ptr, cBranchName, target.cast_ptr, cForce) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newReferenceFromC(ptr, repo).Branch(), nil +} + +func (b *Branch) Delete() error { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_branch_delete(b.Reference.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (b *Branch) Move(newBranchName string, force bool) (*Branch, error) { + var ptr *C.git_reference + cNewBranchName := C.CString(newBranchName) + defer C.free(unsafe.Pointer(cNewBranchName)) + cForce := cbool(force) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newReferenceFromC(ptr, b.repo).Branch(), nil +} + +func (b *Branch) IsHead() (bool, error) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_is_head(b.Reference.ptr) + switch ret { + case 1: + return true, nil + case 0: + return false, nil + } + return false, MakeGitError(ret) + +} + +func (repo *Repository) LookupBranch(branchName string, bt BranchType) (*Branch, error) { + var ptr *C.git_reference + + cName := C.CString(branchName) + defer C.free(unsafe.Pointer(cName)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_lookup(&ptr, repo.ptr, cName, C.git_branch_t(bt)) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newReferenceFromC(ptr, repo).Branch(), nil +} + +func (b *Branch) Name() (string, error) { + var cName *C.char + defer C.free(unsafe.Pointer(cName)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_name(&cName, b.Reference.ptr) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(cName), nil +} + +func (repo *Repository) RemoteName(canonicalBranchName string) (string, error) { + cName := C.CString(canonicalBranchName) + defer C.free(unsafe.Pointer(cName)) + + nameBuf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_remote_name(&nameBuf, repo.ptr, cName) + if ret < 0 { + return "", MakeGitError(ret) + } + defer C.git_buf_free(&nameBuf) + + return C.GoString(nameBuf.ptr), nil +} + +func (b *Branch) SetUpstream(upstreamName string) error { + cName := C.CString(upstreamName) + defer C.free(unsafe.Pointer(cName)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_set_upstream(b.Reference.ptr, cName) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (b *Branch) Upstream() (*Reference, error) { + + var ptr *C.git_reference + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_upstream(&ptr, b.Reference.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newReferenceFromC(ptr, b.repo), nil +} + +func (repo *Repository) UpstreamName(canonicalBranchName string) (string, error) { + cName := C.CString(canonicalBranchName) + defer C.free(unsafe.Pointer(cName)) + + nameBuf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_upstream_name(&nameBuf, repo.ptr, cName) + if ret < 0 { + return "", MakeGitError(ret) + } + defer C.git_buf_free(&nameBuf) + + return C.GoString(nameBuf.ptr), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/checkout.go b/vendor/gopkg.in/libgit2/git2go.v23/checkout.go new file mode 100644 index 0000000..a2e312b --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/checkout.go @@ -0,0 +1,164 @@ +package git + +/* +#include +*/ +import "C" +import ( + "os" + "runtime" + "unsafe" +) + +type CheckoutStrategy uint + +const ( + CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates + CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data + CheckoutForce CheckoutStrategy = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index + CheckoutRecreateMissing CheckoutStrategy = C.GIT_CHECKOUT_RECREATE_MISSING // Allow checkout to recreate missing files + CheckoutAllowConflicts CheckoutStrategy = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found + CheckoutRemoveUntracked CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored) + CheckoutRemoveIgnored CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index + CheckoutUpdateOnly CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones + CheckoutDontUpdateIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that + CheckoutNoRefresh CheckoutStrategy = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout + CheckoutSkipUnmerged CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files + CheckoutUserOurs CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index + CheckoutUseTheirs CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index + CheckoutDisablePathspecMatch CheckoutStrategy = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths + CheckoutSkipLockedDirectories CheckoutStrategy = C.GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES // Ignore directories in use, they will be left empty + CheckoutDontOverwriteIgnored CheckoutStrategy = C.GIT_CHECKOUT_DONT_OVERWRITE_IGNORED // Don't overwrite ignored files that exist in the checkout target + CheckoutConflictStyleMerge CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_MERGE // Write normal merge files for conflicts + CheckoutConflictStyleDiff3 CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 // Include common ancestor data in diff3 format files for conflicts + CheckoutDontRemoveExisting CheckoutStrategy = C.GIT_CHECKOUT_DONT_REMOVE_EXISTING // Don't overwrite existing files or folders + CheckoutDontWriteIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_WRITE_INDEX // Normally checkout writes the index upon completion; this prevents that + CheckoutUpdateSubmodules CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED) + CheckoutUpdateSubmodulesIfChanged CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) +) + +type CheckoutOpts struct { + Strategy CheckoutStrategy // Default will be a dry run + DisableFilters bool // Don't apply filters like CRLF conversion + DirMode os.FileMode // Default is 0755 + FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob + FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY + TargetDirectory string // Alternative checkout path to workdir + Paths []string + Baseline *Tree +} + +func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts { + opts := CheckoutOpts{} + opts.Strategy = CheckoutStrategy(c.checkout_strategy) + opts.DisableFilters = c.disable_filters != 0 + opts.DirMode = os.FileMode(c.dir_mode) + opts.FileMode = os.FileMode(c.file_mode) + opts.FileOpenFlags = int(c.file_open_flags) + if c.target_directory != nil { + opts.TargetDirectory = C.GoString(c.target_directory) + } + return opts +} + +func (opts *CheckoutOpts) toC() *C.git_checkout_options { + if opts == nil { + return nil + } + c := C.git_checkout_options{} + populateCheckoutOpts(&c, opts) + return &c +} + +// Convert the CheckoutOpts struct to the corresponding +// C-struct. Returns a pointer to ptr, or nil if opts is nil, in order +// to help with what to pass. +func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.git_checkout_options { + if opts == nil { + return nil + } + + C.git_checkout_init_options(ptr, 1) + ptr.checkout_strategy = C.uint(opts.Strategy) + ptr.disable_filters = cbool(opts.DisableFilters) + ptr.dir_mode = C.uint(opts.DirMode.Perm()) + ptr.file_mode = C.uint(opts.FileMode.Perm()) + if opts.TargetDirectory != "" { + ptr.target_directory = C.CString(opts.TargetDirectory) + } + if len(opts.Paths) > 0 { + ptr.paths.strings = makeCStringsFromStrings(opts.Paths) + ptr.paths.count = C.size_t(len(opts.Paths)) + } + + if opts.Baseline != nil { + ptr.baseline = opts.Baseline.cast_ptr + } + + return ptr +} + +func freeCheckoutOpts(ptr *C.git_checkout_options) { + if ptr == nil { + return + } + C.free(unsafe.Pointer(ptr.target_directory)) + if ptr.paths.count > 0 { + freeStrarray(&ptr.paths) + } +} + +// Updates files in the index and the working tree to match the content of +// the commit pointed at by HEAD. opts may be nil. +func (v *Repository) CheckoutHead(opts *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cOpts := opts.toC() + defer freeCheckoutOpts(cOpts) + + ret := C.git_checkout_head(v.ptr, cOpts) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +// Updates files in the working tree to match the content of the given +// index. If index is nil, the repository's index will be used. opts +// may be nil. +func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOpts) error { + var iptr *C.git_index = nil + if index != nil { + iptr = index.ptr + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cOpts := opts.toC() + defer freeCheckoutOpts(cOpts) + + ret := C.git_checkout_index(v.ptr, iptr, cOpts) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Repository) CheckoutTree(tree *Tree, opts *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cOpts := opts.toC() + defer freeCheckoutOpts(cOpts) + + ret := C.git_checkout_tree(v.ptr, tree.ptr, cOpts) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/cherrypick.go b/vendor/gopkg.in/libgit2/git2go.v23/cherrypick.go new file mode 100644 index 0000000..afc1b7e --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/cherrypick.go @@ -0,0 +1,73 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" +) + +type CherrypickOptions struct { + Version uint + Mainline uint + MergeOpts MergeOptions + CheckoutOpts CheckoutOpts +} + +func cherrypickOptionsFromC(c *C.git_cherrypick_options) CherrypickOptions { + opts := CherrypickOptions{ + Version: uint(c.version), + Mainline: uint(c.mainline), + MergeOpts: mergeOptionsFromC(&c.merge_opts), + CheckoutOpts: checkoutOptionsFromC(&c.checkout_opts), + } + return opts +} + +func (opts *CherrypickOptions) toC() *C.git_cherrypick_options { + if opts == nil { + return nil + } + c := C.git_cherrypick_options{} + c.version = C.uint(opts.Version) + c.mainline = C.uint(opts.Mainline) + c.merge_opts = *opts.MergeOpts.toC() + c.checkout_opts = *opts.CheckoutOpts.toC() + return &c +} + +func freeCherrypickOpts(ptr *C.git_cherrypick_options) { + if ptr == nil { + return + } + freeCheckoutOpts(&ptr.checkout_opts) +} + +func DefaultCherrypickOptions() (CherrypickOptions, error) { + c := C.git_cherrypick_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_cherrypick_init_options(&c, C.GIT_CHERRYPICK_OPTIONS_VERSION) + if ecode < 0 { + return CherrypickOptions{}, MakeGitError(ecode) + } + defer freeCherrypickOpts(&c) + return cherrypickOptionsFromC(&c), nil +} + +func (v *Repository) Cherrypick(commit *Commit, opts CherrypickOptions) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cOpts := opts.toC() + defer freeCherrypickOpts(cOpts) + + ecode := C.git_cherrypick(v.ptr, commit.cast_ptr, cOpts) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/clone.go b/vendor/gopkg.in/libgit2/git2go.v23/clone.go new file mode 100644 index 0000000..e80d14d --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/clone.go @@ -0,0 +1,110 @@ +package git + +/* +#include + +extern void _go_git_populate_remote_cb(git_clone_options *opts); +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type RemoteCreateCallback func(repo *Repository, name, url string) (*Remote, ErrorCode) + +type CloneOptions struct { + *CheckoutOpts + *FetchOptions + Bare bool + CheckoutBranch string + RemoteCreateCallback RemoteCreateCallback +} + +func Clone(url string, path string, options *CloneOptions) (*Repository, error) { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + copts := (*C.git_clone_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_clone_options{})))) + populateCloneOptions(copts, options) + defer freeCloneOptions(copts) + + if len(options.CheckoutBranch) != 0 { + copts.checkout_branch = C.CString(options.CheckoutBranch) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_repository + ret := C.git_clone(&ptr, curl, cpath, copts) + freeCheckoutOpts(&copts.checkout_opts) + + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newRepositoryFromC(ptr), nil +} + +//export remoteCreateCallback +func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, curl *C.char, payload unsafe.Pointer) C.int { + name := C.GoString(cname) + url := C.GoString(curl) + repo := newRepositoryFromC((*C.git_repository)(crepo)) + // We don't own this repository, so make sure we don't try to free it + runtime.SetFinalizer(repo, nil) + + if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok { + remote, err := opts.RemoteCreateCallback(repo, name, url) + // clear finalizer as the calling C function will + // free the remote itself + runtime.SetFinalizer(remote, nil) + + if err == ErrOk && remote != nil { + cptr := (**C.git_remote)(cremote) + *cptr = remote.ptr + } else if err == ErrOk && remote == nil { + panic("no remote created by callback") + } + + return C.int(err) + } else { + panic("invalid remote create callback") + } +} + +func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { + C.git_clone_init_options(ptr, C.GIT_CLONE_OPTIONS_VERSION) + + if opts == nil { + return + } + populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) + populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions) + ptr.bare = cbool(opts.Bare) + + if opts.RemoteCreateCallback != nil { + // Go v1.1 does not allow to assign a C function pointer + C._go_git_populate_remote_cb(ptr) + ptr.remote_cb_payload = pointerHandles.Track(*opts) + } +} + +func freeCloneOptions(ptr *C.git_clone_options) { + if ptr == nil { + return + } + + freeCheckoutOpts(&ptr.checkout_opts) + + if ptr.remote_cb_payload != nil { + pointerHandles.Untrack(ptr.remote_cb_payload) + } + + C.free(unsafe.Pointer(ptr.checkout_branch)) + C.free(unsafe.Pointer(ptr)) +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/commit.go b/vendor/gopkg.in/libgit2/git2go.v23/commit.go new file mode 100644 index 0000000..52f7c01 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/commit.go @@ -0,0 +1,110 @@ +package git + +/* +#include + +extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr); +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +// Commit +type Commit struct { + gitObject + cast_ptr *C.git_commit +} + +func (c Commit) Message() string { + return C.GoString(C.git_commit_message(c.cast_ptr)) +} + +func (c Commit) Summary() string { + return C.GoString(C.git_commit_summary(c.cast_ptr)) +} + +func (c Commit) Tree() (*Tree, error) { + var ptr *C.git_tree + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_commit_tree(&ptr, c.cast_ptr) + if err < 0 { + return nil, MakeGitError(err) + } + + return allocObject((*C.git_object)(ptr), c.repo).(*Tree), nil +} + +func (c Commit) TreeId() *Oid { + return newOidFromC(C.git_commit_tree_id(c.cast_ptr)) +} + +func (c Commit) Author() *Signature { + cast_ptr := C.git_commit_author(c.cast_ptr) + return newSignatureFromC(cast_ptr) +} + +func (c Commit) Committer() *Signature { + cast_ptr := C.git_commit_committer(c.cast_ptr) + return newSignatureFromC(cast_ptr) +} + +func (c *Commit) Parent(n uint) *Commit { + var cobj *C.git_commit + ret := C.git_commit_parent(&cobj, c.cast_ptr, C.uint(n)) + if ret != 0 { + return nil + } + + return allocObject((*C.git_object)(cobj), c.repo).(*Commit) +} + +func (c *Commit) ParentId(n uint) *Oid { + return newOidFromC(C.git_commit_parent_id(c.cast_ptr, C.uint(n))) +} + +func (c *Commit) ParentCount() uint { + return uint(C.git_commit_parentcount(c.cast_ptr)) +} + +func (c *Commit) Amend(refname string, author, committer *Signature, message string, tree *Tree) (*Oid, error) { + var cref *C.char + if refname == "" { + cref = nil + } else { + cref = C.CString(refname) + defer C.free(unsafe.Pointer(cref)) + } + + cmsg := C.CString(message) + defer C.free(unsafe.Pointer(cmsg)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + authorSig, err := author.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(committerSig) + + oid := new(Oid) + + cerr := C.git_commit_amend(oid.toC(), c.cast_ptr, cref, authorSig, committerSig, nil, cmsg, tree.cast_ptr) + if cerr < 0 { + return nil, MakeGitError(cerr) + } + + return oid, nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/config.go b/vendor/gopkg.in/libgit2/git2go.v23/config.go new file mode 100644 index 0000000..c4c4028 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/config.go @@ -0,0 +1,414 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type ConfigLevel int + +const ( + // System-wide configuration file; /etc/gitconfig on Linux systems + ConfigLevelSystem ConfigLevel = C.GIT_CONFIG_LEVEL_SYSTEM + + // XDG compatible configuration file; typically ~/.config/git/config + ConfigLevelXDG ConfigLevel = C.GIT_CONFIG_LEVEL_XDG + + // User-specific configuration file (also called Global configuration + // file); typically ~/.gitconfig + ConfigLevelGlobal ConfigLevel = C.GIT_CONFIG_LEVEL_GLOBAL + + // Repository specific configuration file; $WORK_DIR/.git/config on + // non-bare repos + ConfigLevelLocal ConfigLevel = C.GIT_CONFIG_LEVEL_LOCAL + + // Application specific configuration file; freely defined by applications + ConfigLevelApp ConfigLevel = C.GIT_CONFIG_LEVEL_APP + + // Represents the highest level available config file (i.e. the most + // specific config file available that actually is loaded) + ConfigLevelHighest ConfigLevel = C.GIT_CONFIG_HIGHEST_LEVEL +) + +type ConfigEntry struct { + Name string + Value string + Level ConfigLevel +} + +func newConfigEntryFromC(centry *C.git_config_entry) *ConfigEntry { + return &ConfigEntry{ + Name: C.GoString(centry.name), + Value: C.GoString(centry.value), + Level: ConfigLevel(centry.level), + } +} + +type Config struct { + ptr *C.git_config +} + +// NewConfig creates a new empty configuration object +func NewConfig() (*Config, error) { + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_new(&config.ptr); ret < 0 { + return nil, MakeGitError(ret) + } + + return config, nil +} + +// AddFile adds a file-backed backend to the config object at the specified level. +func (c *Config) AddFile(path string, level ConfigLevel, force bool) error { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_add_file_ondisk(c.ptr, cpath, C.git_config_level_t(level), cbool(force)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) LookupInt32(name string) (int32, error) { + var out C.int32_t + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_get_int32(&out, c.ptr, cname) + if ret < 0 { + return 0, MakeGitError(ret) + } + + return int32(out), nil +} + +func (c *Config) LookupInt64(name string) (int64, error) { + var out C.int64_t + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_get_int64(&out, c.ptr, cname) + if ret < 0 { + return 0, MakeGitError(ret) + } + + return int64(out), nil +} + +func (c *Config) LookupString(name string) (string, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + valBuf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_get_string_buf(&valBuf, c.ptr, cname); ret < 0 { + return "", MakeGitError(ret) + } + defer C.git_buf_free(&valBuf) + + return C.GoString(valBuf.ptr), nil +} + +func (c *Config) LookupBool(name string) (bool, error) { + var out C.int + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_get_bool(&out, c.ptr, cname) + if ret < 0 { + return false, MakeGitError(ret) + } + + return out != 0, nil +} + +func (c *Config) NewMultivarIterator(name, regexp string) (*ConfigIterator, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + var cregexp *C.char + if regexp == "" { + cregexp = nil + } else { + cregexp = C.CString(regexp) + defer C.free(unsafe.Pointer(cregexp)) + } + + iter := new(ConfigIterator) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_multivar_iterator_new(&iter.ptr, c.ptr, cname, cregexp) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(iter, (*ConfigIterator).Free) + return iter, nil +} + +// NewIterator creates an iterator over each entry in the +// configuration +func (c *Config) NewIterator() (*ConfigIterator, error) { + iter := new(ConfigIterator) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_iterator_new(&iter.ptr, c.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return iter, nil +} + +// NewIteratorGlob creates an iterator over each entry in the +// configuration whose name matches the given regular expression +func (c *Config) NewIteratorGlob(regexp string) (*ConfigIterator, error) { + iter := new(ConfigIterator) + cregexp := C.CString(regexp) + defer C.free(unsafe.Pointer(cregexp)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_iterator_glob_new(&iter.ptr, c.ptr, cregexp) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return iter, nil +} + +func (c *Config) SetString(name, value string) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + cvalue := C.CString(value) + defer C.free(unsafe.Pointer(cvalue)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_string(c.ptr, cname, cvalue) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) Free() { + runtime.SetFinalizer(c, nil) + C.git_config_free(c.ptr) +} + +func (c *Config) SetInt32(name string, value int32) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_int32(c.ptr, cname, C.int32_t(value)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) SetInt64(name string, value int64) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_int64(c.ptr, cname, C.int64_t(value)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) SetBool(name string, value bool) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_bool(c.ptr, cname, cbool(value)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) SetMultivar(name, regexp, value string) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + cregexp := C.CString(regexp) + defer C.free(unsafe.Pointer(cregexp)) + + cvalue := C.CString(value) + defer C.free(unsafe.Pointer(cvalue)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_multivar(c.ptr, cname, cregexp, cvalue) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) Delete(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_delete_entry(c.ptr, cname) + + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +// OpenLevel creates a single-level focused config object from a multi-level one +func (c *Config) OpenLevel(parent *Config, level ConfigLevel) (*Config, error) { + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_open_level(&config.ptr, parent.ptr, C.git_config_level_t(level)) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return config, nil +} + +// OpenOndisk creates a new config instance containing a single on-disk file +func OpenOndisk(parent *Config, path string) (*Config, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_open_ondisk(&config.ptr, cpath); ret < 0 { + return nil, MakeGitError(ret) + } + + return config, nil +} + +type ConfigIterator struct { + ptr *C.git_config_iterator +} + +// Next returns the next entry for this iterator +func (iter *ConfigIterator) Next() (*ConfigEntry, error) { + var centry *C.git_config_entry + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_next(¢ry, iter.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newConfigEntryFromC(centry), nil +} + +func (iter *ConfigIterator) Free() { + runtime.SetFinalizer(iter, nil) + C.free(unsafe.Pointer(iter.ptr)) +} + +func ConfigFindGlobal() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_global(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} + +func ConfigFindSystem() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_system(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} + +func ConfigFindXDG() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_xdg(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/credentials.go b/vendor/gopkg.in/libgit2/git2go.v23/credentials.go new file mode 100644 index 0000000..bb0ec41 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/credentials.go @@ -0,0 +1,73 @@ +package git + +/* +#include +*/ +import "C" +import "unsafe" + +type CredType uint + +const ( + CredTypeUserpassPlaintext CredType = C.GIT_CREDTYPE_USERPASS_PLAINTEXT + CredTypeSshKey CredType = C.GIT_CREDTYPE_SSH_KEY + CredTypeSshCustom CredType = C.GIT_CREDTYPE_SSH_CUSTOM + CredTypeDefault CredType = C.GIT_CREDTYPE_DEFAULT +) + +type Cred struct { + ptr *C.git_cred +} + +func (o *Cred) HasUsername() bool { + if C.git_cred_has_username(o.ptr) == 1 { + return true + } + return false +} + +func (o *Cred) Type() CredType { + return (CredType)(o.ptr.credtype) +} + +func credFromC(ptr *C.git_cred) *Cred { + return &Cred{ptr} +} + +func NewCredUserpassPlaintext(username string, password string) (int, Cred) { + cred := Cred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + cpassword := C.CString(password) + defer C.free(unsafe.Pointer(cpassword)) + ret := C.git_cred_userpass_plaintext_new(&cred.ptr, cusername, cpassword) + return int(ret), cred +} + +func NewCredSshKey(username string, publickey string, privatekey string, passphrase string) (int, Cred) { + cred := Cred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + cpublickey := C.CString(publickey) + defer C.free(unsafe.Pointer(cpublickey)) + cprivatekey := C.CString(privatekey) + defer C.free(unsafe.Pointer(cprivatekey)) + cpassphrase := C.CString(passphrase) + defer C.free(unsafe.Pointer(cpassphrase)) + ret := C.git_cred_ssh_key_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase) + return int(ret), cred +} + +func NewCredSshKeyFromAgent(username string) (int, Cred) { + cred := Cred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + ret := C.git_cred_ssh_key_from_agent(&cred.ptr, cusername) + return int(ret), cred +} + +func NewCredDefault() (int, Cred) { + cred := Cred{} + ret := C.git_cred_default_new(&cred.ptr) + return int(ret), cred +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/describe.go b/vendor/gopkg.in/libgit2/git2go.v23/describe.go new file mode 100644 index 0000000..c6f9a79 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/describe.go @@ -0,0 +1,222 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +// DescribeOptions represents the describe operation configuration. +// +// You can use DefaultDescribeOptions() to get default options. +type DescribeOptions struct { + // How many tags as candidates to consider to describe the input commit-ish. + // Increasing it above 10 will take slightly longer but may produce a more + // accurate result. 0 will cause only exact matches to be output. + MaxCandidatesTags uint // default: 10 + + // By default describe only shows annotated tags. Change this in order + // to show all refs from refs/tags or refs/. + Strategy DescribeOptionsStrategy // default: DescribeDefault + + // Only consider tags matching the given glob(7) pattern, excluding + // the "refs/tags/" prefix. Can be used to avoid leaking private + // tags from the repo. + Pattern string + + // When calculating the distance from the matching tag or + // reference, only walk down the first-parent ancestry. + OnlyFollowFirstParent bool + + // If no matching tag or reference is found, the describe + // operation would normally fail. If this option is set, it + // will instead fall back to showing the full id of the commit. + ShowCommitOidAsFallback bool +} + +// DefaultDescribeOptions returns default options for the describe operation. +func DefaultDescribeOptions() (DescribeOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + opts := C.git_describe_options{} + ecode := C.git_describe_init_options(&opts, C.GIT_DESCRIBE_OPTIONS_VERSION) + if ecode < 0 { + return DescribeOptions{}, MakeGitError(ecode) + } + + return DescribeOptions{ + MaxCandidatesTags: uint(opts.max_candidates_tags), + Strategy: DescribeOptionsStrategy(opts.describe_strategy), + }, nil +} + +// DescribeFormatOptions can be used for formatting the describe string. +// +// You can use DefaultDescribeFormatOptions() to get default options. +type DescribeFormatOptions struct { + // Size of the abbreviated commit id to use. This value is the + // lower bound for the length of the abbreviated string. + AbbreviatedSize uint // default: 7 + + // Set to use the long format even when a shorter name could be used. + AlwaysUseLongFormat bool + + // If the workdir is dirty and this is set, this string will be + // appended to the description string. + DirtySuffix string +} + +// DefaultDescribeFormatOptions returns default options for formatting +// the output. +func DefaultDescribeFormatOptions() (DescribeFormatOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + opts := C.git_describe_format_options{} + ecode := C.git_describe_init_format_options(&opts, C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION) + if ecode < 0 { + return DescribeFormatOptions{}, MakeGitError(ecode) + } + + return DescribeFormatOptions{ + AbbreviatedSize: uint(opts.abbreviated_size), + AlwaysUseLongFormat: opts.always_use_long_format == 1, + }, nil +} + +// DescribeOptionsStrategy behaves like the --tags and --all options +// to git-describe, namely they say to look for any reference in +// either refs/tags/ or refs/ respectively. +// +// By default it only shows annotated tags. +type DescribeOptionsStrategy uint + +// Describe strategy options. +const ( + DescribeDefault DescribeOptionsStrategy = C.GIT_DESCRIBE_DEFAULT + DescribeTags DescribeOptionsStrategy = C.GIT_DESCRIBE_TAGS + DescribeAll DescribeOptionsStrategy = C.GIT_DESCRIBE_ALL +) + +// Describe performs the describe operation on the commit. +func (c *Commit) Describe(opts *DescribeOptions) (*DescribeResult, error) { + var resultPtr *C.git_describe_result + + var cDescribeOpts *C.git_describe_options + if opts != nil { + var cpattern *C.char + if len(opts.Pattern) > 0 { + cpattern = C.CString(opts.Pattern) + defer C.free(unsafe.Pointer(cpattern)) + } + + cDescribeOpts = &C.git_describe_options{ + version: C.GIT_DESCRIBE_OPTIONS_VERSION, + max_candidates_tags: C.uint(opts.MaxCandidatesTags), + describe_strategy: C.uint(opts.Strategy), + pattern: cpattern, + only_follow_first_parent: cbool(opts.OnlyFollowFirstParent), + show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback), + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_describe_commit(&resultPtr, c.gitObject.ptr, cDescribeOpts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newDescribeResultFromC(resultPtr), nil +} + +// DescribeWorkdir describes the working tree. It means describe HEAD +// and appends (-dirty by default) if the working tree is dirty. +func (repo *Repository) DescribeWorkdir(opts *DescribeOptions) (*DescribeResult, error) { + var resultPtr *C.git_describe_result + + var cDescribeOpts *C.git_describe_options + if opts != nil { + var cpattern *C.char + if len(opts.Pattern) > 0 { + cpattern = C.CString(opts.Pattern) + defer C.free(unsafe.Pointer(cpattern)) + } + + cDescribeOpts = &C.git_describe_options{ + version: C.GIT_DESCRIBE_OPTIONS_VERSION, + max_candidates_tags: C.uint(opts.MaxCandidatesTags), + describe_strategy: C.uint(opts.Strategy), + pattern: cpattern, + only_follow_first_parent: cbool(opts.OnlyFollowFirstParent), + show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback), + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_describe_workdir(&resultPtr, repo.ptr, cDescribeOpts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newDescribeResultFromC(resultPtr), nil +} + +// DescribeResult represents the output from the 'git_describe_commit' +// and 'git_describe_workdir' functions in libgit2. +// +// Use Format() to get a string out of it. +type DescribeResult struct { + ptr *C.git_describe_result +} + +func newDescribeResultFromC(ptr *C.git_describe_result) *DescribeResult { + result := &DescribeResult{ + ptr: ptr, + } + runtime.SetFinalizer(result, (*DescribeResult).Free) + return result +} + +// Format prints the DescribeResult as a string. +func (result *DescribeResult) Format(opts *DescribeFormatOptions) (string, error) { + resultBuf := C.git_buf{} + + var cFormatOpts *C.git_describe_format_options + if opts != nil { + cDirtySuffix := C.CString(opts.DirtySuffix) + defer C.free(unsafe.Pointer(cDirtySuffix)) + + cFormatOpts = &C.git_describe_format_options{ + version: C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, + abbreviated_size: C.uint(opts.AbbreviatedSize), + always_use_long_format: cbool(opts.AlwaysUseLongFormat), + dirty_suffix: cDirtySuffix, + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_describe_format(&resultBuf, result.ptr, cFormatOpts) + if ecode < 0 { + return "", MakeGitError(ecode) + } + defer C.git_buf_free(&resultBuf) + + return C.GoString(resultBuf.ptr), nil +} + +// Free cleans up the C reference. +func (result *DescribeResult) Free() { + runtime.SetFinalizer(result, nil) + C.git_describe_result_free(result.ptr) + result.ptr = nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/diff.go b/vendor/gopkg.in/libgit2/git2go.v23/diff.go new file mode 100644 index 0000000..de56374 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/diff.go @@ -0,0 +1,720 @@ +package git + +/* +#include + +extern int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload); +extern void _go_git_setup_diff_notify_callbacks(git_diff_options* opts); +extern int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const char *new_path, git_diff_options *opts, int eachFile, int eachHunk, int eachLine, void *payload); +*/ +import "C" +import ( + "errors" + "runtime" + "unsafe" +) + +type DiffFlag int + +const ( + DiffFlagBinary DiffFlag = C.GIT_DIFF_FLAG_BINARY + DiffFlagNotBinary DiffFlag = C.GIT_DIFF_FLAG_NOT_BINARY + DiffFlagValidOid DiffFlag = C.GIT_DIFF_FLAG_VALID_ID +) + +type Delta int + +const ( + DeltaUnmodified Delta = C.GIT_DELTA_UNMODIFIED + DeltaAdded Delta = C.GIT_DELTA_ADDED + DeltaDeleted Delta = C.GIT_DELTA_DELETED + DeltaModified Delta = C.GIT_DELTA_MODIFIED + DeltaRenamed Delta = C.GIT_DELTA_RENAMED + DeltaCopied Delta = C.GIT_DELTA_COPIED + DeltaIgnored Delta = C.GIT_DELTA_IGNORED + DeltaUntracked Delta = C.GIT_DELTA_UNTRACKED + DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE +) + +type DiffLineType int + +const ( + DiffLineContext DiffLineType = C.GIT_DIFF_LINE_CONTEXT + DiffLineAddition DiffLineType = C.GIT_DIFF_LINE_ADDITION + DiffLineDeletion DiffLineType = C.GIT_DIFF_LINE_DELETION + DiffLineContextEOFNL DiffLineType = C.GIT_DIFF_LINE_CONTEXT_EOFNL + DiffLineAddEOFNL DiffLineType = C.GIT_DIFF_LINE_ADD_EOFNL + DiffLineDelEOFNL DiffLineType = C.GIT_DIFF_LINE_DEL_EOFNL + + DiffLineFileHdr DiffLineType = C.GIT_DIFF_LINE_FILE_HDR + DiffLineHunkHdr DiffLineType = C.GIT_DIFF_LINE_HUNK_HDR + DiffLineBinary DiffLineType = C.GIT_DIFF_LINE_BINARY +) + +type DiffFile struct { + Path string + Oid *Oid + Size int + Flags DiffFlag + Mode uint16 +} + +func diffFileFromC(file *C.git_diff_file) DiffFile { + return DiffFile{ + Path: C.GoString(file.path), + Oid: newOidFromC(&file.id), + Size: int(file.size), + Flags: DiffFlag(file.flags), + Mode: uint16(file.mode), + } +} + +type DiffDelta struct { + Status Delta + Flags DiffFlag + Similarity uint16 + OldFile DiffFile + NewFile DiffFile +} + +func diffDeltaFromC(delta *C.git_diff_delta) DiffDelta { + return DiffDelta{ + Status: Delta(delta.status), + Flags: DiffFlag(delta.flags), + Similarity: uint16(delta.similarity), + OldFile: diffFileFromC(&delta.old_file), + NewFile: diffFileFromC(&delta.new_file), + } +} + +type DiffHunk struct { + OldStart int + OldLines int + NewStart int + NewLines int + Header string +} + +func diffHunkFromC(hunk *C.git_diff_hunk) DiffHunk { + return DiffHunk{ + OldStart: int(hunk.old_start), + OldLines: int(hunk.old_lines), + NewStart: int(hunk.new_start), + NewLines: int(hunk.new_lines), + Header: C.GoStringN(&hunk.header[0], C.int(hunk.header_len)), + } +} + +type DiffLine struct { + Origin DiffLineType + OldLineno int + NewLineno int + NumLines int + Content string +} + +func diffLineFromC(line *C.git_diff_line) DiffLine { + return DiffLine{ + Origin: DiffLineType(line.origin), + OldLineno: int(line.old_lineno), + NewLineno: int(line.new_lineno), + NumLines: int(line.num_lines), + Content: C.GoStringN(line.content, C.int(line.content_len)), + } +} + +type Diff struct { + ptr *C.git_diff +} + +func (diff *Diff) NumDeltas() (int, error) { + if diff.ptr == nil { + return -1, ErrInvalid + } + return int(C.git_diff_num_deltas(diff.ptr)), nil +} + +func (diff *Diff) GetDelta(index int) (DiffDelta, error) { + if diff.ptr == nil { + return DiffDelta{}, ErrInvalid + } + ptr := C.git_diff_get_delta(diff.ptr, C.size_t(index)) + return diffDeltaFromC(ptr), nil +} + +func newDiffFromC(ptr *C.git_diff) *Diff { + if ptr == nil { + return nil + } + + diff := &Diff{ + ptr: ptr, + } + + runtime.SetFinalizer(diff, (*Diff).Free) + return diff +} + +func (diff *Diff) Free() error { + if diff.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(diff, nil) + C.git_diff_free(diff.ptr) + diff.ptr = nil + return nil +} + +func (diff *Diff) FindSimilar(opts *DiffFindOptions) error { + + var copts *C.git_diff_find_options + if opts != nil { + copts = &C.git_diff_find_options{ + version: C.GIT_DIFF_FIND_OPTIONS_VERSION, + flags: C.uint32_t(opts.Flags), + rename_threshold: C.uint16_t(opts.RenameThreshold), + copy_threshold: C.uint16_t(opts.CopyThreshold), + rename_from_rewrite_threshold: C.uint16_t(opts.RenameFromRewriteThreshold), + break_rewrite_threshold: C.uint16_t(opts.BreakRewriteThreshold), + rename_limit: C.size_t(opts.RenameLimit), + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_find_similar(diff.ptr, copts) + if ecode < 0 { + return MakeGitError(ecode) + } + + return nil +} + +type DiffStats struct { + ptr *C.git_diff_stats +} + +func (stats *DiffStats) Free() error { + if stats.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(stats, nil) + C.git_diff_stats_free(stats.ptr) + stats.ptr = nil + return nil +} + +func (stats *DiffStats) Insertions() int { + return int(C.git_diff_stats_insertions(stats.ptr)) +} + +func (stats *DiffStats) Deletions() int { + return int(C.git_diff_stats_deletions(stats.ptr)) +} + +func (stats *DiffStats) FilesChanged() int { + return int(C.git_diff_stats_files_changed(stats.ptr)) +} + +func (diff *Diff) Stats() (*DiffStats, error) { + stats := new(DiffStats) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ecode := C.git_diff_get_stats(&stats.ptr, diff.ptr); ecode < 0 { + return nil, MakeGitError(ecode) + } + runtime.SetFinalizer(stats, (*DiffStats).Free) + + return stats, nil +} + +type diffForEachData struct { + FileCallback DiffForEachFileCallback + HunkCallback DiffForEachHunkCallback + LineCallback DiffForEachLineCallback + Error error +} + +type DiffForEachFileCallback func(DiffDelta, float64) (DiffForEachHunkCallback, error) + +type DiffDetail int + +const ( + DiffDetailFiles DiffDetail = iota + DiffDetailHunks + DiffDetailLines +) + +func (diff *Diff) ForEach(cbFile DiffForEachFileCallback, detail DiffDetail) error { + if diff.ptr == nil { + return ErrInvalid + } + + intHunks := C.int(0) + if detail >= DiffDetailHunks { + intHunks = C.int(1) + } + + intLines := C.int(0) + if detail >= DiffDetailLines { + intLines = C.int(1) + } + + data := &diffForEachData{ + FileCallback: cbFile, + } + + handle := pointerHandles.Track(data) + defer pointerHandles.Untrack(handle) + + ecode := C._go_git_diff_foreach(diff.ptr, 1, intHunks, intLines, handle) + if ecode < 0 { + return data.Error + } + return nil +} + +//export diffForEachFileCb +func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffForEachData) + if !ok { + panic("could not retrieve data for handle") + } + + data.HunkCallback = nil + if data.FileCallback != nil { + cb, err := data.FileCallback(diffDeltaFromC(delta), float64(progress)) + if err != nil { + data.Error = err + return -1 + } + data.HunkCallback = cb + } + + return 0 +} + +type DiffForEachHunkCallback func(DiffHunk) (DiffForEachLineCallback, error) + +//export diffForEachHunkCb +func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffForEachData) + if !ok { + panic("could not retrieve data for handle") + } + + data.LineCallback = nil + if data.HunkCallback != nil { + cb, err := data.HunkCallback(diffHunkFromC(hunk)) + if err != nil { + data.Error = err + return -1 + } + data.LineCallback = cb + } + + return 0 +} + +type DiffForEachLineCallback func(DiffLine) error + +//export diffForEachLineCb +func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffForEachData) + if !ok { + panic("could not retrieve data for handle") + } + + err := data.LineCallback(diffLineFromC(line)) + if err != nil { + data.Error = err + return -1 + } + + return 0 +} + +func (diff *Diff) Patch(deltaIndex int) (*Patch, error) { + if diff.ptr == nil { + return nil, ErrInvalid + } + var patchPtr *C.git_patch + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_patch_from_diff(&patchPtr, diff.ptr, C.size_t(deltaIndex)) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newPatchFromC(patchPtr), nil +} + +type DiffOptionsFlag int + +const ( + DiffNormal DiffOptionsFlag = C.GIT_DIFF_NORMAL + DiffReverse DiffOptionsFlag = C.GIT_DIFF_REVERSE + DiffIncludeIgnored DiffOptionsFlag = C.GIT_DIFF_INCLUDE_IGNORED + DiffRecurseIgnoredDirs DiffOptionsFlag = C.GIT_DIFF_RECURSE_IGNORED_DIRS + DiffIncludeUntracked DiffOptionsFlag = C.GIT_DIFF_INCLUDE_UNTRACKED + DiffRecurseUntracked DiffOptionsFlag = C.GIT_DIFF_RECURSE_UNTRACKED_DIRS + DiffIncludeUnmodified DiffOptionsFlag = C.GIT_DIFF_INCLUDE_UNMODIFIED + DiffIncludeTypeChange DiffOptionsFlag = C.GIT_DIFF_INCLUDE_TYPECHANGE + DiffIncludeTypeChangeTrees DiffOptionsFlag = C.GIT_DIFF_INCLUDE_TYPECHANGE_TREES + DiffIgnoreFilemode DiffOptionsFlag = C.GIT_DIFF_IGNORE_FILEMODE + DiffIgnoreSubmodules DiffOptionsFlag = C.GIT_DIFF_IGNORE_SUBMODULES + DiffIgnoreCase DiffOptionsFlag = C.GIT_DIFF_IGNORE_CASE + + DiffDisablePathspecMatch DiffOptionsFlag = C.GIT_DIFF_DISABLE_PATHSPEC_MATCH + DiffSkipBinaryCheck DiffOptionsFlag = C.GIT_DIFF_SKIP_BINARY_CHECK + DiffEnableFastUntrackedDirs DiffOptionsFlag = C.GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS + + DiffForceText DiffOptionsFlag = C.GIT_DIFF_FORCE_TEXT + DiffForceBinary DiffOptionsFlag = C.GIT_DIFF_FORCE_BINARY + + DiffIgnoreWhitespace DiffOptionsFlag = C.GIT_DIFF_IGNORE_WHITESPACE + DiffIgnoreWhitespaceChange DiffOptionsFlag = C.GIT_DIFF_IGNORE_WHITESPACE_CHANGE + DiffIgnoreWitespaceEol DiffOptionsFlag = C.GIT_DIFF_IGNORE_WHITESPACE_EOL + + DiffShowUntrackedContent DiffOptionsFlag = C.GIT_DIFF_SHOW_UNTRACKED_CONTENT + DiffShowUnmodified DiffOptionsFlag = C.GIT_DIFF_SHOW_UNMODIFIED + DiffPatience DiffOptionsFlag = C.GIT_DIFF_PATIENCE + DiffMinimal DiffOptionsFlag = C.GIT_DIFF_MINIMAL +) + +type DiffNotifyCallback func(diffSoFar *Diff, deltaToAdd DiffDelta, matchedPathspec string) error + +type DiffOptions struct { + Flags DiffOptionsFlag + IgnoreSubmodules SubmoduleIgnore + Pathspec []string + NotifyCallback DiffNotifyCallback + + ContextLines uint32 + InterhunkLines uint32 + IdAbbrev uint16 + + MaxSize int + + OldPrefix string + NewPrefix string +} + +func DefaultDiffOptions() (DiffOptions, error) { + opts := C.git_diff_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_init_options(&opts, C.GIT_DIFF_OPTIONS_VERSION) + if ecode < 0 { + return DiffOptions{}, MakeGitError(ecode) + } + + return DiffOptions{ + Flags: DiffOptionsFlag(opts.flags), + IgnoreSubmodules: SubmoduleIgnore(opts.ignore_submodules), + Pathspec: makeStringsFromCStrings(opts.pathspec.strings, int(opts.pathspec.count)), + ContextLines: uint32(opts.context_lines), + InterhunkLines: uint32(opts.interhunk_lines), + IdAbbrev: uint16(opts.id_abbrev), + MaxSize: int(opts.max_size), + OldPrefix: "a", + NewPrefix: "b", + }, nil +} + +type DiffFindOptionsFlag int + +const ( + DiffFindByConfig DiffFindOptionsFlag = C.GIT_DIFF_FIND_BY_CONFIG + DiffFindRenames DiffFindOptionsFlag = C.GIT_DIFF_FIND_RENAMES + DiffFindRenamesFromRewrites DiffFindOptionsFlag = C.GIT_DIFF_FIND_RENAMES_FROM_REWRITES + DiffFindCopies DiffFindOptionsFlag = C.GIT_DIFF_FIND_COPIES + DiffFindCopiesFromUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED + DiffFindRewrites DiffFindOptionsFlag = C.GIT_DIFF_FIND_REWRITES + DiffFindBreakRewrites DiffFindOptionsFlag = C.GIT_DIFF_BREAK_REWRITES + DiffFindAndBreakRewrites DiffFindOptionsFlag = C.GIT_DIFF_FIND_AND_BREAK_REWRITES + DiffFindForUntracked DiffFindOptionsFlag = C.GIT_DIFF_FIND_FOR_UNTRACKED + DiffFindAll DiffFindOptionsFlag = C.GIT_DIFF_FIND_ALL + DiffFindIgnoreLeadingWhitespace DiffFindOptionsFlag = C.GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE + DiffFindIgnoreWhitespace DiffFindOptionsFlag = C.GIT_DIFF_FIND_IGNORE_WHITESPACE + DiffFindDontIgnoreWhitespace DiffFindOptionsFlag = C.GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE + DiffFindExactMatchOnly DiffFindOptionsFlag = C.GIT_DIFF_FIND_EXACT_MATCH_ONLY + DiffFindBreakRewritesForRenamesOnly DiffFindOptionsFlag = C.GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY + DiffFindRemoveUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_REMOVE_UNMODIFIED +) + +//TODO implement git_diff_similarity_metric +type DiffFindOptions struct { + Flags DiffFindOptionsFlag + RenameThreshold uint16 + CopyThreshold uint16 + RenameFromRewriteThreshold uint16 + BreakRewriteThreshold uint16 + RenameLimit uint +} + +func DefaultDiffFindOptions() (DiffFindOptions, error) { + opts := C.git_diff_find_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_find_init_options(&opts, C.GIT_DIFF_FIND_OPTIONS_VERSION) + if ecode < 0 { + return DiffFindOptions{}, MakeGitError(ecode) + } + + return DiffFindOptions{ + Flags: DiffFindOptionsFlag(opts.flags), + RenameThreshold: uint16(opts.rename_threshold), + CopyThreshold: uint16(opts.copy_threshold), + RenameFromRewriteThreshold: uint16(opts.rename_from_rewrite_threshold), + BreakRewriteThreshold: uint16(opts.break_rewrite_threshold), + RenameLimit: uint(opts.rename_limit), + }, nil +} + +var ( + ErrDeltaSkip = errors.New("Skip delta") +) + +type diffNotifyData struct { + Callback DiffNotifyCallback + Diff *Diff + Error error +} + +//export diffNotifyCb +func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, matched_pathspec *C.char, handle unsafe.Pointer) int { + diff_so_far := (*C.git_diff)(_diff_so_far) + + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffNotifyData) + if !ok { + panic("could not retrieve data for handle") + } + + if data != nil { + if data.Diff == nil { + data.Diff = newDiffFromC(diff_so_far) + } + + err := data.Callback(data.Diff, diffDeltaFromC(delta_to_add), C.GoString(matched_pathspec)) + + if err == ErrDeltaSkip { + return 1 + } else if err != nil { + data.Error = err + return -1 + } else { + return 0 + } + } + return 0 +} + +func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *diffNotifyData) { + cpathspec := C.git_strarray{} + if opts != nil { + notifyData = &diffNotifyData{ + Callback: opts.NotifyCallback, + } + + if opts.Pathspec != nil { + cpathspec.count = C.size_t(len(opts.Pathspec)) + cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) + } + + copts = &C.git_diff_options{ + version: C.GIT_DIFF_OPTIONS_VERSION, + flags: C.uint32_t(opts.Flags), + ignore_submodules: C.git_submodule_ignore_t(opts.IgnoreSubmodules), + pathspec: cpathspec, + context_lines: C.uint32_t(opts.ContextLines), + interhunk_lines: C.uint32_t(opts.InterhunkLines), + id_abbrev: C.uint16_t(opts.IdAbbrev), + max_size: C.git_off_t(opts.MaxSize), + old_prefix: C.CString(opts.OldPrefix), + new_prefix: C.CString(opts.NewPrefix), + } + + if opts.NotifyCallback != nil { + C._go_git_setup_diff_notify_callbacks(copts) + copts.notify_payload = pointerHandles.Track(notifyData) + } + } + return +} + +func freeDiffOptions(copts *C.git_diff_options) { + if copts != nil { + cpathspec := copts.pathspec + freeStrarray(&cpathspec) + C.free(unsafe.Pointer(copts.old_prefix)) + C.free(unsafe.Pointer(copts.new_prefix)) + if copts.notify_payload != nil { + pointerHandles.Untrack(copts.notify_payload) + } + } +} + +func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var oldPtr, newPtr *C.git_tree + + if oldTree != nil { + oldPtr = oldTree.cast_ptr + } + + if newTree != nil { + newPtr = newTree.cast_ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_tree_to_tree(&diffPtr, v.ptr, oldPtr, newPtr, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + if notifyData != nil && notifyData.Diff != nil { + return notifyData.Diff, nil + } + return newDiffFromC(diffPtr), nil +} + +func (v *Repository) DiffTreeToWorkdir(oldTree *Tree, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var oldPtr *C.git_tree + + if oldTree != nil { + oldPtr = oldTree.cast_ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_tree_to_workdir(&diffPtr, v.ptr, oldPtr, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + if notifyData != nil && notifyData.Diff != nil { + return notifyData.Diff, nil + } + return newDiffFromC(diffPtr), nil +} + +func (v *Repository) DiffTreeToWorkdirWithIndex(oldTree *Tree, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var oldPtr *C.git_tree + + if oldTree != nil { + oldPtr = oldTree.cast_ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_tree_to_workdir_with_index(&diffPtr, v.ptr, oldPtr, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + if notifyData != nil && notifyData.Diff != nil { + return notifyData.Diff, nil + } + return newDiffFromC(diffPtr), nil +} + +func (v *Repository) DiffIndexToWorkdir(index *Index, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var indexPtr *C.git_index + + if index != nil { + indexPtr = index.ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_index_to_workdir(&diffPtr, v.ptr, indexPtr, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + if notifyData != nil && notifyData.Diff != nil { + return notifyData.Diff, nil + } + return newDiffFromC(diffPtr), nil +} + +// DiffBlobs performs a diff between two arbitrary blobs. You can pass +// whatever file names you'd like for them to appear as in the diff. +func DiffBlobs(oldBlob *Blob, oldAsPath string, newBlob *Blob, newAsPath string, opts *DiffOptions, fileCallback DiffForEachFileCallback, detail DiffDetail) error { + data := &diffForEachData{ + FileCallback: fileCallback, + } + + intHunks := C.int(0) + if detail >= DiffDetailHunks { + intHunks = C.int(1) + } + + intLines := C.int(0) + if detail >= DiffDetailLines { + intLines = C.int(1) + } + + handle := pointerHandles.Track(data) + defer pointerHandles.Untrack(handle) + + var oldBlobPtr, newBlobPtr *C.git_blob + if oldBlob != nil { + oldBlobPtr = oldBlob.cast_ptr + } + if newBlob != nil { + newBlobPtr = newBlob.cast_ptr + } + + oldBlobPath := C.CString(oldAsPath) + defer C.free(unsafe.Pointer(oldBlobPath)) + newBlobPath := C.CString(newAsPath) + defer C.free(unsafe.Pointer(newBlobPath)) + + copts, _ := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C._go_git_diff_blobs(oldBlobPtr, oldBlobPath, newBlobPtr, newBlobPath, copts, 1, intHunks, intLines, handle) + if ecode < 0 { + return MakeGitError(ecode) + } + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/git.go b/vendor/gopkg.in/libgit2/git2go.v23/git.go new file mode 100644 index 0000000..7c7f99c --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/git.go @@ -0,0 +1,295 @@ +package git + +/* +#include +#include +#cgo pkg-config: libgit2 +*/ +import "C" +import ( + "bytes" + "encoding/hex" + "errors" + "runtime" + "strings" + "unsafe" +) + +type ErrorClass int + +const ( + ErrClassNone ErrorClass = C.GITERR_NONE + ErrClassNoMemory ErrorClass = C.GITERR_NOMEMORY + ErrClassOs ErrorClass = C.GITERR_OS + ErrClassInvalid ErrorClass = C.GITERR_INVALID + ErrClassReference ErrorClass = C.GITERR_REFERENCE + ErrClassZlib ErrorClass = C.GITERR_ZLIB + ErrClassRepository ErrorClass = C.GITERR_REPOSITORY + ErrClassConfig ErrorClass = C.GITERR_CONFIG + ErrClassRegex ErrorClass = C.GITERR_REGEX + ErrClassOdb ErrorClass = C.GITERR_ODB + ErrClassIndex ErrorClass = C.GITERR_INDEX + ErrClassObject ErrorClass = C.GITERR_OBJECT + ErrClassNet ErrorClass = C.GITERR_NET + ErrClassTag ErrorClass = C.GITERR_TAG + ErrClassTree ErrorClass = C.GITERR_TREE + ErrClassIndexer ErrorClass = C.GITERR_INDEXER + ErrClassSSL ErrorClass = C.GITERR_SSL + ErrClassSubmodule ErrorClass = C.GITERR_SUBMODULE + ErrClassThread ErrorClass = C.GITERR_THREAD + ErrClassStash ErrorClass = C.GITERR_STASH + ErrClassCheckout ErrorClass = C.GITERR_CHECKOUT + ErrClassFetchHead ErrorClass = C.GITERR_FETCHHEAD + ErrClassMerge ErrorClass = C.GITERR_MERGE + ErrClassSsh ErrorClass = C.GITERR_SSH + ErrClassFilter ErrorClass = C.GITERR_FILTER + ErrClassRevert ErrorClass = C.GITERR_REVERT + ErrClassCallback ErrorClass = C.GITERR_CALLBACK +) + +type ErrorCode int + +const ( + + // No error + ErrOk ErrorCode = C.GIT_OK + // Generic error + ErrGeneric ErrorCode = C.GIT_ERROR + // Requested object could not be found + ErrNotFound ErrorCode = C.GIT_ENOTFOUND + // Object exists preventing operation + ErrExists ErrorCode = C.GIT_EEXISTS + // More than one object matches + ErrAmbigious ErrorCode = C.GIT_EAMBIGUOUS + // Output buffer too short to hold data + ErrBuffs ErrorCode = C.GIT_EBUFS + // GIT_EUSER is a special error that is never generated by libgit2 + // code. You can return it from a callback (e.g to stop an iteration) + // to know that it was generated by the callback and not by libgit2. + ErrUser ErrorCode = C.GIT_EUSER + // Operation not allowed on bare repository + ErrBareRepo ErrorCode = C.GIT_EBAREREPO + // HEAD refers to branch with no commits + ErrUnbornBranch ErrorCode = C.GIT_EUNBORNBRANCH + // Merge in progress prevented operation + ErrUnmerged ErrorCode = C.GIT_EUNMERGED + // Reference was not fast-forwardable + ErrNonFastForward ErrorCode = C.GIT_ENONFASTFORWARD + // Name/ref spec was not in a valid format + ErrInvalidSpec ErrorCode = C.GIT_EINVALIDSPEC + // Checkout conflicts prevented operation + ErrConflict ErrorCode = C.GIT_ECONFLICT + // Lock file prevented operation + ErrLocked ErrorCode = C.GIT_ELOCKED + // Reference value does not match expected + ErrModified ErrorCode = C.GIT_EMODIFIED + // Internal only + ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH + // Signals end of iteration with iterator + ErrIterOver ErrorCode = C.GIT_ITEROVER + // Authentication failed + ErrAuth ErrorCode = C.GIT_EAUTH +) + +var ( + ErrInvalid = errors.New("Invalid state for operation") +) + +var pointerHandles *HandleList + +func init() { + pointerHandles = NewHandleList() + + C.git_libgit2_init() + + // This is not something we should be doing, as we may be + // stomping all over someone else's setup. The user should do + // this themselves or use some binding/wrapper which does it + // in such a way that they can be sure they're the only ones + // setting it up. + C.git_openssl_set_locking() +} + +// Oid represents the id for a Git object. +type Oid [20]byte + +func newOidFromC(coid *C.git_oid) *Oid { + if coid == nil { + return nil + } + + oid := new(Oid) + copy(oid[0:20], C.GoBytes(unsafe.Pointer(coid), 20)) + return oid +} + +func NewOidFromBytes(b []byte) *Oid { + oid := new(Oid) + copy(oid[0:20], b[0:20]) + return oid +} + +func (oid *Oid) toC() *C.git_oid { + return (*C.git_oid)(unsafe.Pointer(oid)) +} + +func NewOid(s string) (*Oid, error) { + if len(s) > C.GIT_OID_HEXSZ { + return nil, errors.New("string is too long for oid") + } + + o := new(Oid) + + slice, error := hex.DecodeString(s) + if error != nil { + return nil, error + } + + if len(slice) != 20 { + return nil, &GitError{"Invalid Oid", ErrClassNone, ErrGeneric} + } + + copy(o[:], slice[:20]) + return o, nil +} + +func (oid *Oid) String() string { + return hex.EncodeToString(oid[:]) +} + +func (oid *Oid) Cmp(oid2 *Oid) int { + return bytes.Compare(oid[:], oid2[:]) +} + +func (oid *Oid) Copy() *Oid { + ret := new(Oid) + copy(ret[:], oid[:]) + return ret +} + +func (oid *Oid) Equal(oid2 *Oid) bool { + return bytes.Equal(oid[:], oid2[:]) +} + +func (oid *Oid) IsZero() bool { + for _, a := range oid { + if a != 0 { + return false + } + } + return true +} + +func (oid *Oid) NCmp(oid2 *Oid, n uint) int { + return bytes.Compare(oid[:n], oid2[:n]) +} + +func ShortenOids(ids []*Oid, minlen int) (int, error) { + shorten := C.git_oid_shorten_new(C.size_t(minlen)) + if shorten == nil { + panic("Out of memory") + } + defer C.git_oid_shorten_free(shorten) + + var ret C.int + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + for _, id := range ids { + buf := make([]byte, 41) + C.git_oid_fmt((*C.char)(unsafe.Pointer(&buf[0])), id.toC()) + buf[40] = 0 + ret = C.git_oid_shorten_add(shorten, (*C.char)(unsafe.Pointer(&buf[0]))) + if ret < 0 { + return int(ret), MakeGitError(ret) + } + } + return int(ret), nil +} + +type GitError struct { + Message string + Class ErrorClass + Code ErrorCode +} + +func (e GitError) Error() string { + return e.Message +} + +func IsErrorClass(err error, c ErrorClass) bool { + + if err == nil { + return false + } + if gitError, ok := err.(*GitError); ok { + return gitError.Class == c + } + return false +} + +func IsErrorCode(err error, c ErrorCode) bool { + if err == nil { + return false + } + if gitError, ok := err.(*GitError); ok { + return gitError.Code == c + } + return false +} + +func MakeGitError(errorCode C.int) error { + + var errMessage string + var errClass ErrorClass + if errorCode != C.GIT_ITEROVER { + err := C.giterr_last() + if err != nil { + errMessage = C.GoString(err.message) + errClass = ErrorClass(err.klass) + } else { + errClass = ErrClassInvalid + } + } + return &GitError{errMessage, errClass, ErrorCode(errorCode)} +} + +func MakeGitError2(err int) error { + return MakeGitError(C.int(err)) +} + +func cbool(b bool) C.int { + if b { + return C.int(1) + } + return C.int(0) +} + +func ucbool(b bool) C.uint { + if b { + return C.uint(1) + } + return C.uint(0) +} + +func Discover(start string, across_fs bool, ceiling_dirs []string) (string, error) { + ceildirs := C.CString(strings.Join(ceiling_dirs, string(C.GIT_PATH_LIST_SEPARATOR))) + defer C.free(unsafe.Pointer(ceildirs)) + + cstart := C.CString(start) + defer C.free(unsafe.Pointer(cstart)) + + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_discover(&buf, cstart, cbool(across_fs), ceildirs) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/graph.go b/vendor/gopkg.in/libgit2/git2go.v23/graph.go new file mode 100644 index 0000000..e5d7732 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/graph.go @@ -0,0 +1,36 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" +) + +func (repo *Repository) DescendantOf(commit, ancestor *Oid) (bool, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_graph_descendant_of(repo.ptr, commit.toC(), ancestor.toC()) + if ret < 0 { + return false, MakeGitError(ret) + } + + return (ret > 0), nil +} + +func (repo *Repository) AheadBehind(local, upstream *Oid) (ahead, behind int, err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var aheadT C.size_t + var behindT C.size_t + + ret := C.git_graph_ahead_behind(&aheadT, &behindT, repo.ptr, local.toC(), upstream.toC()) + if ret < 0 { + return 0, 0, MakeGitError(ret) + } + + return int(aheadT), int(behindT), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/handles.go b/vendor/gopkg.in/libgit2/git2go.v23/handles.go new file mode 100644 index 0000000..d27d3c3 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/handles.go @@ -0,0 +1,57 @@ +package git + +/* +#include +*/ +import "C" +import ( + "fmt" + "sync" + "unsafe" +) + +type HandleList struct { + sync.RWMutex + // stores the Go pointers + handles map[unsafe.Pointer]interface{} +} + +func NewHandleList() *HandleList { + return &HandleList{ + handles: make(map[unsafe.Pointer]interface{}), + } +} + +// Track adds the given pointer to the list of pointers to track and +// returns a pointer value which can be passed to C as an opaque +// pointer. +func (v *HandleList) Track(pointer interface{}) unsafe.Pointer { + handle := C.malloc(1) + + v.Lock() + v.handles[handle] = pointer + v.Unlock() + + return handle +} + +// Untrack stops tracking the pointer given by the handle +func (v *HandleList) Untrack(handle unsafe.Pointer) { + v.Lock() + delete(v.handles, handle) + C.free(handle) + v.Unlock() +} + +// Get retrieves the pointer from the given handle +func (v *HandleList) Get(handle unsafe.Pointer) interface{} { + v.RLock() + defer v.RUnlock() + + ptr, ok := v.handles[handle] + if !ok { + panic(fmt.Sprintf("invalid pointer handle: %p", handle)) + } + + return ptr +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/index.go b/vendor/gopkg.in/libgit2/git2go.v23/index.go new file mode 100644 index 0000000..1eb5e9d --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/index.go @@ -0,0 +1,471 @@ +package git + +/* +#include + +extern int _go_git_index_add_all(git_index*, const git_strarray*, unsigned int, void*); +extern int _go_git_index_update_all(git_index*, const git_strarray*, void*); +extern int _go_git_index_remove_all(git_index*, const git_strarray*, void*); + +*/ +import "C" +import ( + "fmt" + "runtime" + "unsafe" +) + +type IndexMatchedPathCallback func(string, string) int + +type IndexAddOpts uint + +const ( + IndexAddDefault IndexAddOpts = C.GIT_INDEX_ADD_DEFAULT + IndexAddForce IndexAddOpts = C.GIT_INDEX_ADD_FORCE + IndexAddDisablePathspecMatch IndexAddOpts = C.GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH + IndexAddCheckPathspec IndexAddOpts = C.GIT_INDEX_ADD_CHECK_PATHSPEC +) + +type Index struct { + ptr *C.git_index +} + +type IndexTime struct { + seconds int32 + nanoseconds uint32 +} + +type IndexEntry struct { + Ctime IndexTime + Mtime IndexTime + Mode Filemode + Uid uint32 + Gid uint32 + Size uint32 + Id *Oid + Path string +} + +func newIndexEntryFromC(entry *C.git_index_entry) *IndexEntry { + if entry == nil { + return nil + } + return &IndexEntry{ + IndexTime { int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds) }, + IndexTime { int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds) }, + Filemode(entry.mode), + uint32(entry.uid), + uint32(entry.gid), + uint32(entry.file_size), + newOidFromC(&entry.id), + C.GoString(entry.path), + } +} + +func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) { + dest.ctime.seconds = C.int32_t(source.Ctime.seconds) + dest.ctime.nanoseconds = C.uint32_t(source.Ctime.nanoseconds) + dest.mtime.seconds = C.int32_t(source.Mtime.seconds) + dest.mtime.nanoseconds = C.uint32_t(source.Mtime.nanoseconds) + dest.mode = C.uint32_t(source.Mode) + dest.uid = C.uint32_t(source.Uid) + dest.gid = C.uint32_t(source.Gid) + dest.file_size = C.uint32_t(source.Size) + dest.id = *source.Id.toC() + dest.path = C.CString(source.Path) +} + +func freeCIndexEntry(entry *C.git_index_entry) { + C.free(unsafe.Pointer(entry.path)) +} + +func newIndexFromC(ptr *C.git_index) *Index { + idx := &Index{ptr} + runtime.SetFinalizer(idx, (*Index).Free) + return idx +} + +// NewIndex allocates a new index. It won't be associated with any +// file on the filesystem or repository +func NewIndex() (*Index, error) { + var ptr *C.git_index + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_index_new(&ptr); err < 0 { + return nil, MakeGitError(err) + } + + return newIndexFromC(ptr), nil +} + +// OpenIndex creates a new index at the given path. If the file does +// not exist it will be created when Write() is called. +func OpenIndex(path string) (*Index, error) { + var ptr *C.git_index + + var cpath = C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_index_open(&ptr, cpath); err < 0 { + return nil, MakeGitError(err) + } + + return newIndexFromC(ptr), nil +} + +// Path returns the index' path on disk or an empty string if it +// exists only in memory. +func (v *Index) Path() string { + return C.GoString(C.git_index_path(v.ptr)) +} + +// Add adds or replaces the given entry to the index, making a copy of +// the data +func (v *Index) Add(entry *IndexEntry) error { + var centry C.git_index_entry + + populateCIndexEntry(entry, ¢ry) + defer freeCIndexEntry(¢ry) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_index_add(v.ptr, ¢ry); err < 0 { + return MakeGitError(err) + } + + return nil +} + +func (v *Index) AddByPath(path string) error { + cstr := C.CString(path) + defer C.free(unsafe.Pointer(cstr)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_add_bypath(v.ptr, cstr) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Index) AddAll(pathspecs []string, flags IndexAddOpts, callback IndexMatchedPathCallback) error { + cpathspecs := C.git_strarray{} + cpathspecs.count = C.size_t(len(pathspecs)) + cpathspecs.strings = makeCStringsFromStrings(pathspecs) + defer freeStrarray(&cpathspecs) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var handle unsafe.Pointer + if callback != nil { + handle = pointerHandles.Track(callback) + defer pointerHandles.Untrack(handle) + } + + ret := C._go_git_index_add_all( + v.ptr, + &cpathspecs, + C.uint(flags), + handle, + ) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (v *Index) UpdateAll(pathspecs []string, callback IndexMatchedPathCallback) error { + cpathspecs := C.git_strarray{} + cpathspecs.count = C.size_t(len(pathspecs)) + cpathspecs.strings = makeCStringsFromStrings(pathspecs) + defer freeStrarray(&cpathspecs) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var handle unsafe.Pointer + if callback != nil { + handle = pointerHandles.Track(callback) + defer pointerHandles.Untrack(handle) + } + + ret := C._go_git_index_update_all( + v.ptr, + &cpathspecs, + handle, + ) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (v *Index) RemoveAll(pathspecs []string, callback IndexMatchedPathCallback) error { + cpathspecs := C.git_strarray{} + cpathspecs.count = C.size_t(len(pathspecs)) + cpathspecs.strings = makeCStringsFromStrings(pathspecs) + defer freeStrarray(&cpathspecs) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var handle unsafe.Pointer + if callback != nil { + handle = pointerHandles.Track(callback) + defer pointerHandles.Untrack(handle) + } + + ret := C._go_git_index_remove_all( + v.ptr, + &cpathspecs, + handle, + ) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +//export indexMatchedPathCallback +func indexMatchedPathCallback(cPath, cMatchedPathspec *C.char, payload unsafe.Pointer) int { + if callback, ok := pointerHandles.Get(payload).(IndexMatchedPathCallback); ok { + return callback(C.GoString(cPath), C.GoString(cMatchedPathspec)) + } else { + panic("invalid matched path callback") + } +} + +func (v *Index) RemoveByPath(path string) error { + cstr := C.CString(path) + defer C.free(unsafe.Pointer(cstr)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_remove_bypath(v.ptr, cstr) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Index) WriteTreeTo(repo *Repository) (*Oid, error) { + oid := new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_write_tree_to(oid.toC(), v.ptr, repo.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + +// ReadTree replaces the contents of the index with those of the given +// tree +func (v *Index) ReadTree(tree *Tree) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_read_tree(v.ptr, tree.cast_ptr); + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Index) WriteTree() (*Oid, error) { + oid := new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_write_tree(oid.toC(), v.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + +func (v *Index) Write() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_write(v.ptr) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Index) Free() { + runtime.SetFinalizer(v, nil) + C.git_index_free(v.ptr) +} + +func (v *Index) EntryCount() uint { + return uint(C.git_index_entrycount(v.ptr)) +} + +func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) { + centry := C.git_index_get_byindex(v.ptr, C.size_t(index)) + if centry == nil { + return nil, fmt.Errorf("Index out of Bounds") + } + return newIndexEntryFromC(centry), nil +} + +func (v *Index) HasConflicts() bool { + return C.git_index_has_conflicts(v.ptr) != 0 +} + +// FIXME: this might return an error +func (v *Index) CleanupConflicts() { + C.git_index_conflict_cleanup(v.ptr) +} + +func (v *Index) AddConflict(ancestor *IndexEntry, our *IndexEntry, their *IndexEntry) error { + + var cancestor *C.git_index_entry + var cour *C.git_index_entry + var ctheir *C.git_index_entry + + if ancestor != nil { + cancestor = &C.git_index_entry{} + populateCIndexEntry(ancestor, cancestor) + defer freeCIndexEntry(cancestor) + } + + if our != nil { + cour = &C.git_index_entry{} + populateCIndexEntry(our, cour) + defer freeCIndexEntry(cour) + } + + if their != nil { + ctheir = &C.git_index_entry{} + populateCIndexEntry(their, ctheir) + defer freeCIndexEntry(ctheir) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_add(v.ptr, cancestor, cour, ctheir) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +type IndexConflict struct { + Ancestor *IndexEntry + Our *IndexEntry + Their *IndexEntry +} + +func (v *Index) GetConflict(path string) (IndexConflict, error) { + + var cancestor *C.git_index_entry + var cour *C.git_index_entry + var ctheir *C.git_index_entry + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_get(&cancestor, &cour, &ctheir, v.ptr, cpath) + if ecode < 0 { + return IndexConflict{}, MakeGitError(ecode) + } + return IndexConflict{ + Ancestor: newIndexEntryFromC(cancestor), + Our: newIndexEntryFromC(cour), + Their: newIndexEntryFromC(ctheir), + }, nil +} + +func (v *Index) RemoveConflict(path string) error { + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_remove(v.ptr, cpath) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +type IndexConflictIterator struct { + ptr *C.git_index_conflict_iterator + index *Index +} + +func newIndexConflictIteratorFromC(index *Index, ptr *C.git_index_conflict_iterator) *IndexConflictIterator { + i := &IndexConflictIterator{ptr: ptr, index: index} + runtime.SetFinalizer(i, (*IndexConflictIterator).Free) + return i +} + +func (v *IndexConflictIterator) Index() *Index { + return v.index +} + +func (v *IndexConflictIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_index_conflict_iterator_free(v.ptr) +} + +func (v *Index) ConflictIterator() (*IndexConflictIterator, error) { + var i *C.git_index_conflict_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_iterator_new(&i, v.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newIndexConflictIteratorFromC(v, i), nil +} + +func (v *IndexConflictIterator) Next() (IndexConflict, error) { + var cancestor *C.git_index_entry + var cour *C.git_index_entry + var ctheir *C.git_index_entry + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_next(&cancestor, &cour, &ctheir, v.ptr) + if ecode < 0 { + return IndexConflict{}, MakeGitError(ecode) + } + return IndexConflict{ + Ancestor: newIndexEntryFromC(cancestor), + Our: newIndexEntryFromC(cour), + Their: newIndexEntryFromC(ctheir), + }, nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/merge.go b/vendor/gopkg.in/libgit2/git2go.v23/merge.go new file mode 100644 index 0000000..756c792 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/merge.go @@ -0,0 +1,417 @@ +package git + +/* +#include + +extern git_annotated_commit** _go_git_make_merge_head_array(size_t len); +extern void _go_git_annotated_commit_array_set(git_annotated_commit** array, git_annotated_commit* ptr, size_t n); +extern git_annotated_commit* _go_git_annotated_commit_array_get(git_annotated_commit** array, size_t n); +extern int _go_git_merge_file(git_merge_file_result*, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, git_merge_file_options*); + +*/ +import "C" +import ( + "reflect" + "runtime" + "unsafe" +) + +type AnnotatedCommit struct { + ptr *C.git_annotated_commit +} + +func newAnnotatedCommitFromC(c *C.git_annotated_commit) *AnnotatedCommit { + mh := &AnnotatedCommit{ptr: c} + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) + return mh +} + +func (mh *AnnotatedCommit) Free() { + runtime.SetFinalizer(mh, nil) + C.git_annotated_commit_free(mh.ptr) +} + +func (r *Repository) AnnotatedCommitFromFetchHead(branchName string, remoteURL string, oid *Oid) (*AnnotatedCommit, error) { + mh := &AnnotatedCommit{} + + cbranchName := C.CString(branchName) + defer C.free(unsafe.Pointer(cbranchName)) + + cremoteURL := C.CString(remoteURL) + defer C.free(unsafe.Pointer(cremoteURL)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_annotated_commit_from_fetchhead(&mh.ptr, r.ptr, cbranchName, cremoteURL, oid.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) + return mh, nil +} + +func (r *Repository) LookupAnnotatedCommit(oid *Oid) (*AnnotatedCommit, error) { + mh := &AnnotatedCommit{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_annotated_commit_lookup(&mh.ptr, r.ptr, oid.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) + return mh, nil +} + +func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, error) { + mh := &AnnotatedCommit{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_annotated_commit_from_ref(&mh.ptr, r.ptr, ref.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) + return mh, nil +} + +type MergeTreeFlag int + +const ( + MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_TREE_FIND_RENAMES +) + +type MergeOptions struct { + Version uint + TreeFlags MergeTreeFlag + + RenameThreshold uint + TargetLimit uint + FileFavor MergeFileFavor + + //TODO: Diff similarity metric +} + +func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions { + return MergeOptions{ + Version: uint(opts.version), + TreeFlags: MergeTreeFlag(opts.tree_flags), + RenameThreshold: uint(opts.rename_threshold), + TargetLimit: uint(opts.target_limit), + FileFavor: MergeFileFavor(opts.file_favor), + } +} + +func DefaultMergeOptions() (MergeOptions, error) { + opts := C.git_merge_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_merge_init_options(&opts, C.GIT_MERGE_OPTIONS_VERSION) + if ecode < 0 { + return MergeOptions{}, MakeGitError(ecode) + } + return mergeOptionsFromC(&opts), nil +} + +func (mo *MergeOptions) toC() *C.git_merge_options { + if mo == nil { + return nil + } + return &C.git_merge_options{ + version: C.uint(mo.Version), + tree_flags: C.git_merge_tree_flag_t(mo.TreeFlags), + rename_threshold: C.uint(mo.RenameThreshold), + target_limit: C.uint(mo.TargetLimit), + file_favor: C.git_merge_file_favor_t(mo.FileFavor), + } +} + +type MergeFileFavor int + +const ( + MergeFileFavorNormal MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_NORMAL + MergeFileFavorOurs MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_OURS + MergeFileFavorTheirs MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_THEIRS + MergeFileFavorUnion MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_UNION +) + +func (r *Repository) Merge(theirHeads []*AnnotatedCommit, mergeOptions *MergeOptions, checkoutOptions *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cMergeOpts := mergeOptions.toC() + cCheckoutOpts := checkoutOptions.toC() + defer freeCheckoutOpts(cCheckoutOpts) + + gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads)) + for i := 0; i < len(theirHeads); i++ { + gmerge_head_array[i] = theirHeads[i].ptr + } + ptr := unsafe.Pointer(&gmerge_head_array[0]) + err := C.git_merge(r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads)), cMergeOpts, cCheckoutOpts) + if err < 0 { + return MakeGitError(err) + } + return nil +} + +type MergeAnalysis int + +const ( + MergeAnalysisNone MergeAnalysis = C.GIT_MERGE_ANALYSIS_NONE + MergeAnalysisNormal MergeAnalysis = C.GIT_MERGE_ANALYSIS_NORMAL + MergeAnalysisUpToDate MergeAnalysis = C.GIT_MERGE_ANALYSIS_UP_TO_DATE + MergeAnalysisFastForward MergeAnalysis = C.GIT_MERGE_ANALYSIS_FASTFORWARD + MergeAnalysisUnborn MergeAnalysis = C.GIT_MERGE_ANALYSIS_UNBORN +) + +type MergePreference int + +const ( + MergePreferenceNone MergePreference = C.GIT_MERGE_PREFERENCE_NONE + MergePreferenceNoFastForward MergePreference = C.GIT_MERGE_PREFERENCE_NO_FASTFORWARD + MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY +) + +// MergeAnalysis returns the possible actions which could be taken by +// a 'git-merge' command. There may be multiple answers, so the first +// return value is a bitmask of MergeAnalysis values. +func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads)) + for i := 0; i < len(theirHeads); i++ { + gmerge_head_array[i] = theirHeads[i].ptr + } + ptr := unsafe.Pointer(&gmerge_head_array[0]) + var analysis C.git_merge_analysis_t + var preference C.git_merge_preference_t + err := C.git_merge_analysis(&analysis, &preference, r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads))) + if err < 0 { + return MergeAnalysisNone, MergePreferenceNone, MakeGitError(err) + } + return MergeAnalysis(analysis), MergePreference(preference), nil + +} + +func (r *Repository) MergeCommits(ours *Commit, theirs *Commit, options *MergeOptions) (*Index, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + copts := options.toC() + + idx := &Index{} + + ret := C.git_merge_commits(&idx.ptr, r.ptr, ours.cast_ptr, theirs.cast_ptr, copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(idx, (*Index).Free) + return idx, nil +} + +func (r *Repository) MergeTrees(ancestor *Tree, ours *Tree, theirs *Tree, options *MergeOptions) (*Index, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + copts := options.toC() + + idx := &Index{} + var ancestor_ptr *C.git_tree + if ancestor != nil { + ancestor_ptr = ancestor.cast_ptr + } + ret := C.git_merge_trees(&idx.ptr, r.ptr, ancestor_ptr, ours.cast_ptr, theirs.cast_ptr, copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(idx, (*Index).Free) + return idx, nil +} + +func (r *Repository) MergeBase(one *Oid, two *Oid) (*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var oid C.git_oid + ret := C.git_merge_base(&oid, r.ptr, one.toC(), two.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newOidFromC(&oid), nil +} + +// MergeBases retrieves the list of merge bases between two commits. +// +// If none are found, an empty slice is returned and the error is set +// approprately +func (r *Repository) MergeBases(one, two *Oid) ([]*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var coids C.git_oidarray + ret := C.git_merge_bases(&coids, r.ptr, one.toC(), two.toC()) + if ret < 0 { + return make([]*Oid, 0), MakeGitError(ret) + } + + oids := make([]*Oid, coids.count) + hdr := reflect.SliceHeader { + Data: uintptr(unsafe.Pointer(coids.ids)), + Len: int(coids.count), + Cap: int(coids.count), + } + + goSlice := *(*[]C.git_oid)(unsafe.Pointer(&hdr)) + + for i, cid := range goSlice { + oids[i] = newOidFromC(&cid) + } + + return oids, nil +} + +//TODO: int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]); +//TODO: GIT_EXTERN(int) git_merge_base_octopus(git_oid *out,git_repository *repo,size_t length,const git_oid input_array[]); + +type MergeFileResult struct { + Automergeable bool + Path string + Mode uint + Contents []byte + ptr *C.git_merge_file_result +} + +func newMergeFileResultFromC(c *C.git_merge_file_result) *MergeFileResult { + var path string + if c.path != nil { + path = C.GoString(c.path) + } + + originalBytes := C.GoBytes(unsafe.Pointer(c.ptr), C.int(c.len)) + gobytes := make([]byte, len(originalBytes)) + copy(gobytes, originalBytes) + r := &MergeFileResult{ + Automergeable: c.automergeable != 0, + Path: path, + Mode: uint(c.mode), + Contents: gobytes, + ptr: c, + } + + runtime.SetFinalizer(r, (*MergeFileResult).Free) + return r +} + +func (r *MergeFileResult) Free() { + runtime.SetFinalizer(r, nil) + C.git_merge_file_result_free(r.ptr) +} + +type MergeFileInput struct { + Path string + Mode uint + Contents []byte +} + +type MergeFileFlags int + +const ( + MergeFileDefault MergeFileFlags = C.GIT_MERGE_FILE_DEFAULT + + MergeFileStyleMerge MergeFileFlags = C.GIT_MERGE_FILE_STYLE_MERGE + MergeFileStyleDiff MergeFileFlags = C.GIT_MERGE_FILE_STYLE_DIFF3 + MergeFileStyleSimplifyAlnum MergeFileFlags = C.GIT_MERGE_FILE_SIMPLIFY_ALNUM +) + +type MergeFileOptions struct { + AncestorLabel string + OurLabel string + TheirLabel string + Favor MergeFileFavor + Flags MergeFileFlags +} + +func mergeFileOptionsFromC(c C.git_merge_file_options) MergeFileOptions { + return MergeFileOptions{ + AncestorLabel: C.GoString(c.ancestor_label), + OurLabel: C.GoString(c.our_label), + TheirLabel: C.GoString(c.their_label), + Favor: MergeFileFavor(c.favor), + Flags: MergeFileFlags(c.flags), + } +} + +func populateCMergeFileOptions(c *C.git_merge_file_options, options MergeFileOptions) { + c.ancestor_label = C.CString(options.AncestorLabel) + c.our_label = C.CString(options.OurLabel) + c.their_label = C.CString(options.TheirLabel) + c.favor = C.git_merge_file_favor_t(options.Favor) + c.flags = C.uint(options.Flags) +} + +func freeCMergeFileOptions(c *C.git_merge_file_options) { + C.free(unsafe.Pointer(c.ancestor_label)) + C.free(unsafe.Pointer(c.our_label)) + C.free(unsafe.Pointer(c.their_label)) +} + +func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInput, options *MergeFileOptions) (*MergeFileResult, error) { + + ancestorPath := C.CString(ancestor.Path) + defer C.free(unsafe.Pointer(ancestorPath)) + var ancestorContents *byte + if len(ancestor.Contents) > 0 { + ancestorContents = &ancestor.Contents[0] + } + + oursPath := C.CString(ours.Path) + defer C.free(unsafe.Pointer(oursPath)) + var oursContents *byte + if len(ours.Contents) > 0 { + oursContents = &ours.Contents[0] + } + + theirsPath := C.CString(theirs.Path) + defer C.free(unsafe.Pointer(theirsPath)) + var theirsContents *byte + if len(theirs.Contents) > 0 { + theirsContents = &theirs.Contents[0] + } + + var copts *C.git_merge_file_options + if options != nil { + copts = &C.git_merge_file_options{} + ecode := C.git_merge_file_init_options(copts, C.GIT_MERGE_FILE_OPTIONS_VERSION) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + populateCMergeFileOptions(copts, *options) + defer freeCMergeFileOptions(copts) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var result C.git_merge_file_result + ecode := C._go_git_merge_file(&result, + (*C.char)(unsafe.Pointer(ancestorContents)), C.size_t(len(ancestor.Contents)), ancestorPath, C.uint(ancestor.Mode), + (*C.char)(unsafe.Pointer(oursContents)), C.size_t(len(ours.Contents)), oursPath, C.uint(ours.Mode), + (*C.char)(unsafe.Pointer(theirsContents)), C.size_t(len(theirs.Contents)), theirsPath, C.uint(theirs.Mode), + copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newMergeFileResultFromC(&result), nil + +} + +// TODO: GIT_EXTERN(int) git_merge_file_from_index(git_merge_file_result *out,git_repository *repo,const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts); diff --git a/vendor/gopkg.in/libgit2/git2go.v23/note.go b/vendor/gopkg.in/libgit2/git2go.v23/note.go new file mode 100644 index 0000000..a1b15d8 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/note.go @@ -0,0 +1,220 @@ +package git + +/* +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +// This object represents the possible operations which can be +// performed on the collection of notes for a repository. +type NoteCollection struct { + repo *Repository +} + +// Create adds a note for an object +func (c *NoteCollection) Create( + ref string, author, committer *Signature, id *Oid, + note string, force bool) (*Oid, error) { + + oid := new(Oid) + + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + authorSig, err := author.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(committerSig) + + cnote := C.CString(note) + defer C.free(unsafe.Pointer(cnote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_create( + oid.toC(), c.repo.ptr, cref, authorSig, + committerSig, id.toC(), cnote, cbool(force)) + + if ret < 0 { + return nil, MakeGitError(ret) + } + return oid, nil +} + +// Read reads the note for an object +func (c *NoteCollection) Read(ref string, id *Oid) (*Note, error) { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + note := new(Note) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_read(¬e.ptr, c.repo.ptr, cref, id.toC()); ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(note, (*Note).Free) + return note, nil +} + +// Remove removes the note for an object +func (c *NoteCollection) Remove(ref string, author, committer *Signature, id *Oid) error { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + authorSig, err := author.toC() + if err != nil { + return err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return err + } + defer C.git_signature_free(committerSig) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_remove(c.repo.ptr, cref, authorSig, committerSig, id.toC()) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// DefaultRef returns the default notes reference for a repository +func (c *NoteCollection) DefaultRef() (string, error) { + buf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_default_ref(&buf, c.repo.ptr); ret < 0 { + return "", MakeGitError(ret) + } + + ret := C.GoString(buf.ptr) + C.git_buf_free(&buf) + + return ret, nil +} + +// Note +type Note struct { + ptr *C.git_note +} + +// Free frees a git_note object +func (n *Note) Free() error { + if n.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(n, nil) + C.git_note_free(n.ptr) + n.ptr = nil + return nil +} + +// Author returns the signature of the note author +func (n *Note) Author() *Signature { + ptr := C.git_note_author(n.ptr) + return newSignatureFromC(ptr) +} + +// Id returns the note object's id +func (n *Note) Id() *Oid { + ptr := C.git_note_id(n.ptr) + return newOidFromC(ptr) +} + +// Committer returns the signature of the note committer +func (n *Note) Committer() *Signature { + ptr := C.git_note_committer(n.ptr) + return newSignatureFromC(ptr) +} + +// Message returns the note message +func (n *Note) Message() string { + return C.GoString(C.git_note_message(n.ptr)) +} + +// NoteIterator +type NoteIterator struct { + ptr *C.git_note_iterator +} + +// NewNoteIterator creates a new iterator for notes +func (repo *Repository) NewNoteIterator(ref string) (*NoteIterator, error) { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + var ptr *C.git_note_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_iterator_new(&ptr, repo.ptr, cref); ret < 0 { + return nil, MakeGitError(ret) + } + + iter := &NoteIterator{ptr: ptr} + runtime.SetFinalizer(iter, (*NoteIterator).Free) + return iter, nil +} + +// Free frees the note interator +func (v *NoteIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_note_iterator_free(v.ptr) +} + +// Next returns the current item (note id & annotated id) and advances the +// iterator internally to the next item +func (it *NoteIterator) Next() (noteId, annotatedId *Oid, err error) { + noteId, annotatedId = new(Oid), new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_next(noteId.toC(), annotatedId.toC(), it.ptr); ret < 0 { + err = MakeGitError(ret) + } + return +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/object.go b/vendor/gopkg.in/libgit2/git2go.v23/object.go new file mode 100644 index 0000000..6ecebf8 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/object.go @@ -0,0 +1,138 @@ +package git + +/* +#include +*/ +import "C" +import "runtime" + +type ObjectType int + +const ( + ObjectAny ObjectType = C.GIT_OBJ_ANY + ObjectBad ObjectType = C.GIT_OBJ_BAD + ObjectCommit ObjectType = C.GIT_OBJ_COMMIT + ObjectTree ObjectType = C.GIT_OBJ_TREE + ObjectBlob ObjectType = C.GIT_OBJ_BLOB + ObjectTag ObjectType = C.GIT_OBJ_TAG +) + +type Object interface { + Free() + Id() *Oid + Type() ObjectType + Owner() *Repository + Peel(t ObjectType) (Object, error) +} + +type gitObject struct { + ptr *C.git_object + repo *Repository +} + +func (t ObjectType) String() string { + switch t { + case ObjectAny: + return "Any" + case ObjectBad: + return "Bad" + case ObjectCommit: + return "Commit" + case ObjectTree: + return "Tree" + case ObjectBlob: + return "Blob" + case ObjectTag: + return "Tag" + } + // Never reached + return "" +} + +func (o gitObject) Id() *Oid { + return newOidFromC(C.git_object_id(o.ptr)) +} + +func (o gitObject) Type() ObjectType { + return ObjectType(C.git_object_type(o.ptr)) +} + +// Owner returns a weak reference to the repository which owns this +// object +func (o gitObject) Owner() *Repository { + return &Repository{ + ptr: C.git_object_owner(o.ptr), + } +} + +func (o *gitObject) Free() { + runtime.SetFinalizer(o, nil) + C.git_object_free(o.ptr) +} + +// Peel recursively peels an object until an object of the specified type is met. +// +// If the query cannot be satisfied due to the object model, ErrInvalidSpec +// will be returned (e.g. trying to peel a blob to a tree). +// +// If you pass ObjectAny as the target type, then the object will be peeled +// until the type changes. A tag will be peeled until the referenced object +// is no longer a tag, and a commit will be peeled to a tree. Any other object +// type will return ErrInvalidSpec. +// +// If peeling a tag we discover an object which cannot be peeled to the target +// type due to the object model, an error will be returned. +func (o *gitObject) Peel(t ObjectType) (Object, error) { + var cobj *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_object_peel(&cobj, o.ptr, C.git_otype(t)); err < 0 { + return nil, MakeGitError(err) + } + + return allocObject(cobj, o.repo), nil +} + +func allocObject(cobj *C.git_object, repo *Repository) Object { + obj := gitObject{ + ptr: cobj, + repo: repo, + } + + switch ObjectType(C.git_object_type(cobj)) { + case ObjectCommit: + commit := &Commit{ + gitObject: obj, + cast_ptr: (*C.git_commit)(cobj), + } + runtime.SetFinalizer(commit, (*Commit).Free) + return commit + + case ObjectTree: + tree := &Tree{ + gitObject: obj, + cast_ptr: (*C.git_tree)(cobj), + } + runtime.SetFinalizer(tree, (*Tree).Free) + return tree + + case ObjectBlob: + blob := &Blob{ + gitObject: obj, + cast_ptr: (*C.git_blob)(cobj), + } + runtime.SetFinalizer(blob, (*Blob).Free) + return blob + case ObjectTag: + tag := &Tag{ + gitObject: obj, + cast_ptr: (*C.git_tag)(cobj), + } + runtime.SetFinalizer(tag, (*Tag).Free) + return tag + } + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/odb.go b/vendor/gopkg.in/libgit2/git2go.v23/odb.go new file mode 100644 index 0000000..9c6baa3 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/odb.go @@ -0,0 +1,316 @@ +package git + +/* +#include + +extern int _go_git_odb_foreach(git_odb *db, void *payload); +extern void _go_git_odb_backend_free(git_odb_backend *backend); +*/ +import "C" +import ( + "reflect" + "runtime" + "unsafe" +) + +type Odb struct { + ptr *C.git_odb +} + +type OdbBackend struct { + ptr *C.git_odb_backend +} + +func NewOdb() (odb *Odb, err error) { + odb = new(Odb) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_new(&odb.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(odb, (*Odb).Free) + return odb, nil +} + +func NewOdbBackendFromC(ptr *C.git_odb_backend) (backend *OdbBackend) { + backend = &OdbBackend{ptr} + return backend +} + +func (v *Odb) AddBackend(backend *OdbBackend, priority int) (err error) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_add_backend(v.ptr, backend.ptr, C.int(priority)) + if ret < 0 { + backend.Free() + return MakeGitError(ret) + } + return nil +} + +func (v *Odb) ReadHeader(oid *Oid) (uint64, ObjectType, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var sz C.size_t + var cotype C.git_otype + + ret := C.git_odb_read_header(&sz, &cotype, v.ptr, oid.toC()) + if ret < 0 { + return 0, C.GIT_OBJ_BAD, MakeGitError(ret) + } + + return uint64(sz), ObjectType(cotype), nil +} + +func (v *Odb) Exists(oid *Oid) bool { + ret := C.git_odb_exists(v.ptr, oid.toC()) + return ret != 0 +} + +func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) { + oid = new(Oid) + var cptr unsafe.Pointer + if len(data) > 0 { + cptr = unsafe.Pointer(&data[0]) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_write(oid.toC(), v.ptr, cptr, C.size_t(len(data)), C.git_otype(otype)) + + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + +func (v *Odb) Read(oid *Oid) (obj *OdbObject, err error) { + obj = new(OdbObject) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_read(&obj.ptr, v.ptr, oid.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(obj, (*OdbObject).Free) + return obj, nil +} + +type OdbForEachCallback func(id *Oid) error + +type foreachData struct { + callback OdbForEachCallback + err error +} + +//export odbForEachCb +func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int { + data, ok := pointerHandles.Get(handle).(*foreachData) + + if !ok { + panic("could not retrieve handle") + } + + err := data.callback(newOidFromC(id)) + if err != nil { + data.err = err + return C.GIT_EUSER + } + + return 0 +} + +func (v *Odb) ForEach(callback OdbForEachCallback) error { + data := foreachData{ + callback: callback, + err: nil, + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) + + ret := C._go_git_odb_foreach(v.ptr, handle) + if ret == C.GIT_EUSER { + return data.err + } else if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +// Hash determines the object-ID (sha1) of a data buffer. +func (v *Odb) Hash(data []byte, otype ObjectType) (oid *Oid, err error) { + oid = new(Oid) + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + ptr := unsafe.Pointer(header.Data) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_hash(oid.toC(), ptr, C.size_t(header.Len), C.git_otype(otype)) + if ret < 0 { + return nil, MakeGitError(ret) + } + return oid, nil +} + +// NewReadStream opens a read stream from the ODB. Reading from it will give you the +// contents of the object. +func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) { + stream := new(OdbReadStream) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_open_rstream(&stream.ptr, v.ptr, id.toC()) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(stream, (*OdbReadStream).Free) + return stream, nil +} + +// NewWriteStream opens a write stream to the ODB, which allows you to +// create a new object in the database. The size and type must be +// known in advance +func (v *Odb) NewWriteStream(size int64, otype ObjectType) (*OdbWriteStream, error) { + stream := new(OdbWriteStream) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.git_off_t(size), C.git_otype(otype)) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(stream, (*OdbWriteStream).Free) + return stream, nil +} + +func (v *OdbBackend) Free() { + C._go_git_odb_backend_free(v.ptr) +} + +type OdbObject struct { + ptr *C.git_odb_object +} + +func (v *OdbObject) Free() { + runtime.SetFinalizer(v, nil) + C.git_odb_object_free(v.ptr) +} + +func (object *OdbObject) Id() (oid *Oid) { + return newOidFromC(C.git_odb_object_id(object.ptr)) +} + +func (object *OdbObject) Len() (len uint64) { + return uint64(C.git_odb_object_size(object.ptr)) +} + +func (object *OdbObject) Data() (data []byte) { + var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr) + var blob []byte + + len := int(C.git_odb_object_size(object.ptr)) + + sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&blob))) + sliceHeader.Cap = len + sliceHeader.Len = len + sliceHeader.Data = uintptr(c_blob) + + return blob +} + +type OdbReadStream struct { + ptr *C.git_odb_stream +} + +// Read reads from the stream +func (stream *OdbReadStream) Read(data []byte) (int, error) { + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + ptr := (*C.char)(unsafe.Pointer(header.Data)) + size := C.size_t(header.Cap) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_stream_read(stream.ptr, ptr, size) + if ret < 0 { + return 0, MakeGitError(ret) + } + + header.Len = int(ret) + + return len(data), nil +} + +// Close is a dummy function in order to implement the Closer and +// ReadCloser interfaces +func (stream *OdbReadStream) Close() error { + return nil +} + +func (stream *OdbReadStream) Free() { + runtime.SetFinalizer(stream, nil) + C.git_odb_stream_free(stream.ptr) +} + +type OdbWriteStream struct { + ptr *C.git_odb_stream + Id Oid +} + +// Write writes to the stream +func (stream *OdbWriteStream) Write(data []byte) (int, error) { + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + ptr := (*C.char)(unsafe.Pointer(header.Data)) + size := C.size_t(header.Len) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_stream_write(stream.ptr, ptr, size) + if ret < 0 { + return 0, MakeGitError(ret) + } + + return len(data), nil +} + +// Close signals that all the data has been written and stores the +// resulting object id in the stream's Id field. +func (stream *OdbWriteStream) Close() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_stream_finalize_write(stream.Id.toC(), stream.ptr) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (stream *OdbWriteStream) Free() { + runtime.SetFinalizer(stream, nil) + C.git_odb_stream_free(stream.ptr) +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/packbuilder.go b/vendor/gopkg.in/libgit2/git2go.v23/packbuilder.go new file mode 100644 index 0000000..4dc352c --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/packbuilder.go @@ -0,0 +1,153 @@ +package git + +/* +#include +#include +#include + +extern int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload); +*/ +import "C" +import ( + "io" + "os" + "runtime" + "unsafe" +) + +type Packbuilder struct { + ptr *C.git_packbuilder +} + +func (repo *Repository) NewPackbuilder() (*Packbuilder, error) { + builder := &Packbuilder{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_packbuilder_new(&builder.ptr, repo.ptr) + if ret != 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(builder, (*Packbuilder).Free) + return builder, nil +} + +func (pb *Packbuilder) Free() { + runtime.SetFinalizer(pb, nil) + C.git_packbuilder_free(pb.ptr) +} + +func (pb *Packbuilder) Insert(id *Oid, name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_packbuilder_insert(pb.ptr, id.toC(), cname) + if ret != 0 { + return MakeGitError(ret) + } + return nil +} + +func (pb *Packbuilder) InsertCommit(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_packbuilder_insert_commit(pb.ptr, id.toC()) + if ret != 0 { + return MakeGitError(ret) + } + return nil +} + +func (pb *Packbuilder) InsertTree(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_packbuilder_insert_tree(pb.ptr, id.toC()) + if ret != 0 { + return MakeGitError(ret) + } + return nil +} + +func (pb *Packbuilder) ObjectCount() uint32 { + return uint32(C.git_packbuilder_object_count(pb.ptr)) +} + +func (pb *Packbuilder) WriteToFile(name string, mode os.FileMode) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_packbuilder_write(pb.ptr, cname, C.uint(mode.Perm()), nil, nil) + if ret != 0 { + return MakeGitError(ret) + } + return nil +} + +func (pb *Packbuilder) Write(w io.Writer) error { + return pb.ForEach(func(slice []byte) error { + _, err := w.Write(slice) + return err + }) +} + +func (pb *Packbuilder) Written() uint32 { + return uint32(C.git_packbuilder_written(pb.ptr)) +} + +type PackbuilderForeachCallback func([]byte) error +type packbuilderCbData struct { + callback PackbuilderForeachCallback + err error +} + +//export packbuilderForEachCb +func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*packbuilderCbData) + if !ok { + panic("could not get packbuilder CB data") + } + + slice := C.GoBytes(buf, C.int(size)) + + err := data.callback(slice) + if err != nil { + data.err = err + return C.GIT_EUSER + } + + return 0 +} + +// ForEach repeatedly calls the callback with new packfile data until +// there is no more data or the callback returns an error +func (pb *Packbuilder) ForEach(callback PackbuilderForeachCallback) error { + data := packbuilderCbData{ + callback: callback, + err: nil, + } + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_packbuilder_foreach(pb.ptr, handle) + if err == C.GIT_EUSER { + return data.err + } + if err < 0 { + return MakeGitError(err) + } + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/patch.go b/vendor/gopkg.in/libgit2/git2go.v23/patch.go new file mode 100644 index 0000000..45e14ac --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/patch.go @@ -0,0 +1,87 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type Patch struct { + ptr *C.git_patch +} + +func newPatchFromC(ptr *C.git_patch) *Patch { + if ptr == nil { + return nil + } + + patch := &Patch{ + ptr: ptr, + } + + runtime.SetFinalizer(patch, (*Patch).Free) + return patch +} + +func (patch *Patch) Free() error { + if patch.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(patch, nil) + C.git_patch_free(patch.ptr) + patch.ptr = nil + return nil +} + +func (patch *Patch) String() (string, error) { + if patch.ptr == nil { + return "", ErrInvalid + } + var buf C.git_buf + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_patch_to_buf(&buf, patch.ptr) + if ecode < 0 { + return "", MakeGitError(ecode) + } + return C.GoString(buf.ptr), nil +} + +func toPointer(data []byte) (ptr unsafe.Pointer) { + if len(data) > 0 { + ptr = unsafe.Pointer(&data[0]) + } else { + ptr = unsafe.Pointer(nil) + } + return +} + +func (v *Repository) PatchFromBuffers(oldPath, newPath string, oldBuf, newBuf []byte, opts *DiffOptions) (*Patch, error) { + var patchPtr *C.git_patch + + oldPtr := toPointer(oldBuf) + newPtr := (*C.char)(toPointer(newBuf)) + + cOldPath := C.CString(oldPath) + defer C.free(unsafe.Pointer(cOldPath)) + + cNewPath := C.CString(newPath) + defer C.free(unsafe.Pointer(cNewPath)) + + copts, _ := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_patch_from_buffers(&patchPtr, oldPtr, C.size_t(len(oldBuf)), cOldPath, newPtr, C.size_t(len(newBuf)), cNewPath, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newPatchFromC(patchPtr), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/refdb.go b/vendor/gopkg.in/libgit2/git2go.v23/refdb.go new file mode 100644 index 0000000..0d1e241 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/refdb.go @@ -0,0 +1,56 @@ +package git + +/* +#include +#include + +extern void _go_git_refdb_backend_free(git_refdb_backend *backend); +*/ +import "C" +import ( + "runtime" +) + +type Refdb struct { + ptr *C.git_refdb +} + +type RefdbBackend struct { + ptr *C.git_refdb_backend +} + +func (v *Repository) NewRefdb() (refdb *Refdb, err error) { + refdb = new(Refdb) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_refdb_new(&refdb.ptr, v.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(refdb, (*Refdb).Free) + return refdb, nil +} + +func NewRefdbBackendFromC(ptr *C.git_refdb_backend) (backend *RefdbBackend) { + backend = &RefdbBackend{ptr} + return backend +} + +func (v *Refdb) SetBackend(backend *RefdbBackend) (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_refdb_set_backend(v.ptr, backend.ptr) + if ret < 0 { + backend.Free() + return MakeGitError(ret) + } + return nil +} + +func (v *RefdbBackend) Free() { + C._go_git_refdb_backend_free(v.ptr) +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/reference.go b/vendor/gopkg.in/libgit2/git2go.v23/reference.go new file mode 100644 index 0000000..140082f --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/reference.go @@ -0,0 +1,451 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type ReferenceType int + +const ( + ReferenceSymbolic ReferenceType = C.GIT_REF_SYMBOLIC + ReferenceOid ReferenceType = C.GIT_REF_OID +) + +type Reference struct { + ptr *C.git_reference + repo *Repository +} + +type ReferenceCollection struct { + repo *Repository +} + +func (c *ReferenceCollection) Lookup(name string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_lookup(&ptr, c.repo.ptr, cname) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +func (c *ReferenceCollection) Create(name string, id *Oid, force bool, msg string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_create(&ptr, c.repo.ptr, cname, id.toC(), cbool(force), cmsg) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +func (c *ReferenceCollection) CreateSymbolic(name, target string, force bool, msg string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + ctarget := C.CString(target) + defer C.free(unsafe.Pointer(ctarget)) + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_symbolic_create(&ptr, c.repo.ptr, cname, ctarget, cbool(force), cmsg) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +// EnsureLog ensures that there is a reflog for the given reference +// name and creates an empty one if necessary. +func (c *ReferenceCollection) EnsureLog(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_ensure_log(c.repo.ptr, cname) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +// HasLog returns whether there is a reflog for the given reference +// name +func (c *ReferenceCollection) HasLog(name string) (bool, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_has_log(c.repo.ptr, cname) + if ret < 0 { + return false, MakeGitError(ret) + } + + return ret == 1, nil +} + +// Dwim looks up a reference by DWIMing its short name +func (c *ReferenceCollection) Dwim(name string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_reference + ret := C.git_reference_dwim(&ptr, c.repo.ptr, cname) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +func newReferenceFromC(ptr *C.git_reference, repo *Repository) *Reference { + ref := &Reference{ptr: ptr, repo: repo} + runtime.SetFinalizer(ref, (*Reference).Free) + return ref +} + +func (v *Reference) SetSymbolicTarget(target string, msg string) (*Reference, error) { + var ptr *C.git_reference + + ctarget := C.CString(target) + defer C.free(unsafe.Pointer(ctarget)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, cmsg) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, v.repo), nil +} + +func (v *Reference) SetTarget(target *Oid, msg string) (*Reference, error) { + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), cmsg) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, v.repo), nil +} + +func (v *Reference) Resolve() (*Reference, error) { + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_resolve(&ptr, v.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, v.repo), nil +} + +func (v *Reference) Rename(name string, force bool, msg string) (*Reference, error) { + var ptr *C.git_reference + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), cmsg) + + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, v.repo), nil +} + +func (v *Reference) Target() *Oid { + return newOidFromC(C.git_reference_target(v.ptr)) +} + +func (v *Reference) SymbolicTarget() string { + cstr := C.git_reference_symbolic_target(v.ptr) + if cstr == nil { + return "" + } + + return C.GoString(cstr) +} + +func (v *Reference) Delete() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_delete(v.ptr) + + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Reference) Peel(t ObjectType) (Object, error) { + var cobj *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_reference_peel(&cobj, v.ptr, C.git_otype(t)); err < 0 { + return nil, MakeGitError(err) + } + + return allocObject(cobj, v.repo), nil +} + +// Owner returns a weak reference to the repository which owns this +// reference. +func (v *Reference) Owner() *Repository { + return &Repository{ + ptr: C.git_reference_owner(v.ptr), + } +} + +// Cmp compares both references, retursn 0 on equality, otherwise a +// stable sorting. +func (v *Reference) Cmp(ref2 *Reference) int { + return int(C.git_reference_cmp(v.ptr, ref2.ptr)) +} + +// Shorthand returns a "human-readable" short reference name +func (v *Reference) Shorthand() string { + return C.GoString(C.git_reference_shorthand(v.ptr)) +} + +func (v *Reference) Name() string { + return C.GoString(C.git_reference_name(v.ptr)) +} + +func (v *Reference) Type() ReferenceType { + return ReferenceType(C.git_reference_type(v.ptr)) +} + +func (v *Reference) IsBranch() bool { + return C.git_reference_is_branch(v.ptr) == 1 +} + +func (v *Reference) IsRemote() bool { + return C.git_reference_is_remote(v.ptr) == 1 +} + +func (v *Reference) IsTag() bool { + return C.git_reference_is_tag(v.ptr) == 1 +} + +// IsNote checks if the reference is a note. +func (v *Reference) IsNote() bool { + return C.git_reference_is_note(v.ptr) == 1 +} + +func (v *Reference) Free() { + runtime.SetFinalizer(v, nil) + C.git_reference_free(v.ptr) +} + +type ReferenceIterator struct { + ptr *C.git_reference_iterator + repo *Repository +} + +type ReferenceNameIterator struct { + *ReferenceIterator +} + +// NewReferenceIterator creates a new iterator over reference names +func (repo *Repository) NewReferenceIterator() (*ReferenceIterator, error) { + var ptr *C.git_reference_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_iterator_new(&ptr, repo.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + iter := &ReferenceIterator{ptr: ptr, repo: repo} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter, nil +} + +// NewReferenceIterator creates a new branch iterator over reference names +func (repo *Repository) NewReferenceNameIterator() (*ReferenceNameIterator, error) { + var ptr *C.git_reference_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_iterator_new(&ptr, repo.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + iter := &ReferenceIterator{ptr: ptr, repo: repo} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter.Names(), nil +} + +// NewReferenceIteratorGlob creates an iterator over reference names +// that match the speicified glob. The glob is of the usual fnmatch +// type. +func (repo *Repository) NewReferenceIteratorGlob(glob string) (*ReferenceIterator, error) { + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + var ptr *C.git_reference_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_iterator_glob_new(&ptr, repo.ptr, cstr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + iter := &ReferenceIterator{ptr: ptr} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter, nil +} + +func (i *ReferenceIterator) Names() *ReferenceNameIterator { + return &ReferenceNameIterator{i} +} + +// NextName retrieves the next reference name. If the iteration is over, +// the returned error is git.ErrIterOver +func (v *ReferenceNameIterator) Next() (string, error) { + var ptr *C.char + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_next_name(&ptr, v.ptr) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(ptr), nil +} + +// Next retrieves the next reference. If the iterationis over, the +// returned error is git.ErrIterOver +func (v *ReferenceIterator) Next() (*Reference, error) { + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_next(&ptr, v.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, v.repo), nil +} + +// Free the reference iterator +func (v *ReferenceIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_reference_iterator_free(v.ptr) +} + +// ReferenceIsValidName ensures the reference name is well-formed. +// +// Valid reference names must follow one of two patterns: +// +// 1. Top-level names must contain only capital letters and underscores, +// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). +// +// 2. Names prefixed with "refs/" can be almost anything. You must avoid +// the characters '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences +// ".." and " @ {" which have special meaning to revparse. +func ReferenceIsValidName(name string) bool { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + if C.git_reference_is_valid_name(cname) == 1 { + return true + } + return false +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/remote.go b/vendor/gopkg.in/libgit2/git2go.v23/remote.go new file mode 100644 index 0000000..330f202 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/remote.go @@ -0,0 +1,747 @@ +package git + +/* +#include +#include + +extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks); + +*/ +import "C" +import ( + "crypto/x509" + "reflect" + "runtime" + "strings" + "unsafe" +) + +type TransferProgress struct { + TotalObjects uint + IndexedObjects uint + ReceivedObjects uint + LocalObjects uint + TotalDeltas uint + ReceivedBytes uint +} + +func newTransferProgressFromC(c *C.git_transfer_progress) TransferProgress { + return TransferProgress{ + TotalObjects: uint(c.total_objects), + IndexedObjects: uint(c.indexed_objects), + ReceivedObjects: uint(c.received_objects), + LocalObjects: uint(c.local_objects), + TotalDeltas: uint(c.total_deltas), + ReceivedBytes: uint(c.received_bytes)} +} + +type RemoteCompletion uint +type ConnectDirection uint + +const ( + RemoteCompletionDownload RemoteCompletion = C.GIT_REMOTE_COMPLETION_DOWNLOAD + RemoteCompletionIndexing RemoteCompletion = C.GIT_REMOTE_COMPLETION_INDEXING + RemoteCompletionError RemoteCompletion = C.GIT_REMOTE_COMPLETION_ERROR + + ConnectDirectionFetch ConnectDirection = C.GIT_DIRECTION_FETCH + ConnectDirectionPush ConnectDirection = C.GIT_DIRECTION_PUSH +) + +type TransportMessageCallback func(str string) ErrorCode +type CompletionCallback func(RemoteCompletion) ErrorCode +type CredentialsCallback func(url string, username_from_url string, allowed_types CredType) (ErrorCode, *Cred) +type TransferProgressCallback func(stats TransferProgress) ErrorCode +type UpdateTipsCallback func(refname string, a *Oid, b *Oid) ErrorCode +type CertificateCheckCallback func(cert *Certificate, valid bool, hostname string) ErrorCode +type PackbuilderProgressCallback func(stage int32, current, total uint32) ErrorCode +type PushTransferProgressCallback func(current, total uint32, bytes uint) ErrorCode +type PushUpdateReferenceCallback func(refname, status string) ErrorCode + +type RemoteCallbacks struct { + SidebandProgressCallback TransportMessageCallback + CompletionCallback + CredentialsCallback + TransferProgressCallback + UpdateTipsCallback + CertificateCheckCallback + PackProgressCallback PackbuilderProgressCallback + PushTransferProgressCallback + PushUpdateReferenceCallback +} + +type FetchPrune uint + +const ( + // Use the setting from the configuration + FetchPruneUnspecified FetchPrune = C.GIT_FETCH_PRUNE_UNSPECIFIED + // Force pruning on + FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE + // Force pruning off + FetchNoPrune FetchPrune = C.GIT_FETCH_NO_PRUNE +) + +type DownloadTags uint + +const ( + + // Use the setting from the configuration. + DownloadTagsUnspecified DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED + // Ask the server for tags pointing to objects we're already + // downloading. + DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO + + // Don't ask for any tags beyond the refspecs. + DownloadTagsNone DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_NONE + + // Ask for the all the tags. + DownloadTagsAll DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_ALL +) + +type FetchOptions struct { + // Callbacks to use for this fetch operation + RemoteCallbacks RemoteCallbacks + // Whether to perform a prune after the fetch + Prune FetchPrune + // Whether to write the results to FETCH_HEAD. Defaults to + // on. Leave this default in order to behave like git. + UpdateFetchhead bool + + // Determines how to behave regarding tags on the remote, such + // as auto-downloading tags for objects we're downloading or + // downloading all of them. + // + // The default is to auto-follow tags. + DownloadTags DownloadTags +} + +type Remote struct { + ptr *C.git_remote + callbacks RemoteCallbacks +} + +type CertificateKind uint + +const ( + CertificateX509 CertificateKind = C.GIT_CERT_X509 + CertificateHostkey CertificateKind = C.GIT_CERT_HOSTKEY_LIBSSH2 +) + +// Certificate represents the two possible certificates which libgit2 +// knows it might find. If Kind is CertficateX509 then the X509 field +// will be filled. If Kind is CertificateHostkey then the Hostkey +// field will be fille.d +type Certificate struct { + Kind CertificateKind + X509 *x509.Certificate + Hostkey HostkeyCertificate +} + +type HostkeyKind uint + +const ( + HostkeyMD5 HostkeyKind = C.GIT_CERT_SSH_MD5 + HostkeySHA1 HostkeyKind = C.GIT_CERT_SSH_SHA1 +) + +// Server host key information. If Kind is HostkeyMD5 the MD5 field +// will be filled. If Kind is HostkeySHA1, then HashSHA1 will be +// filled. +type HostkeyCertificate struct { + Kind HostkeyKind + HashMD5 [16]byte + HashSHA1 [20]byte +} + +type PushOptions struct { + // Callbacks to use for this push operation + RemoteCallbacks RemoteCallbacks + + PbParallelism uint +} + +type RemoteHead struct { + Id *Oid + Name string +} + +func newRemoteHeadFromC(ptr *C.git_remote_head) RemoteHead { + return RemoteHead{ + Id: newOidFromC(&ptr.oid), + Name: C.GoString(ptr.name), + } +} + +func untrackCalbacksPayload(callbacks *C.git_remote_callbacks) { + if callbacks != nil && callbacks.payload != nil { + pointerHandles.Untrack(callbacks.payload) + } +} + +func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) { + C.git_remote_init_callbacks(ptr, C.GIT_REMOTE_CALLBACKS_VERSION) + if callbacks == nil { + return + } + C._go_git_setup_callbacks(ptr) + ptr.payload = pointerHandles.Track(callbacks) +} + +//export sidebandProgressCallback +func sidebandProgressCallback(_str *C.char, _len C.int, data unsafe.Pointer) int { + callbacks := pointerHandles.Get(data).(*RemoteCallbacks) + if callbacks.SidebandProgressCallback == nil { + return 0 + } + str := C.GoStringN(_str, _len) + return int(callbacks.SidebandProgressCallback(str)) +} + +//export completionCallback +func completionCallback(completion_type C.git_remote_completion_type, data unsafe.Pointer) int { + callbacks := pointerHandles.Get(data).(*RemoteCallbacks) + if callbacks.CompletionCallback == nil { + return 0 + } + return int(callbacks.CompletionCallback(RemoteCompletion(completion_type))) +} + +//export credentialsCallback +func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C.char, allowed_types uint, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + if callbacks.CredentialsCallback == nil { + return 0 + } + url := C.GoString(_url) + username_from_url := C.GoString(_username_from_url) + ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types)) + if cred != nil { + *_cred = cred.ptr + } + return int(ret) +} + +//export transferProgressCallback +func transferProgressCallback(stats *C.git_transfer_progress, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + if callbacks.TransferProgressCallback == nil { + return 0 + } + return int(callbacks.TransferProgressCallback(newTransferProgressFromC(stats))) +} + +//export updateTipsCallback +func updateTipsCallback(_refname *C.char, _a *C.git_oid, _b *C.git_oid, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + if callbacks.UpdateTipsCallback == nil { + return 0 + } + refname := C.GoString(_refname) + a := newOidFromC(_a) + b := newOidFromC(_b) + return int(callbacks.UpdateTipsCallback(refname, a, b)) +} + +//export certificateCheckCallback +func certificateCheckCallback(_cert *C.git_cert, _valid C.int, _host *C.char, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + // if there's no callback set, we need to make sure we fail if the library didn't consider this cert valid + if callbacks.CertificateCheckCallback == nil { + if _valid == 1 { + return 0 + } else { + return C.GIT_ECERTIFICATE + } + } + host := C.GoString(_host) + valid := _valid != 0 + + var cert Certificate + if _cert.cert_type == C.GIT_CERT_X509 { + cert.Kind = CertificateX509 + ccert := (*C.git_cert_x509)(unsafe.Pointer(_cert)) + x509_certs, err := x509.ParseCertificates(C.GoBytes(ccert.data, C.int(ccert.len))) + if err != nil { + return C.GIT_EUSER + } + + // we assume there's only one, which should hold true for any web server we want to talk to + cert.X509 = x509_certs[0] + } else if _cert.cert_type == C.GIT_CERT_HOSTKEY_LIBSSH2 { + cert.Kind = CertificateHostkey + ccert := (*C.git_cert_hostkey)(unsafe.Pointer(_cert)) + cert.Hostkey.Kind = HostkeyKind(ccert._type) + C.memcpy(unsafe.Pointer(&cert.Hostkey.HashMD5[0]), unsafe.Pointer(&ccert.hash_md5[0]), C.size_t(len(cert.Hostkey.HashMD5))) + C.memcpy(unsafe.Pointer(&cert.Hostkey.HashSHA1[0]), unsafe.Pointer(&ccert.hash_sha1[0]), C.size_t(len(cert.Hostkey.HashSHA1))) + } else { + cstr := C.CString("Unsupported certificate type") + C.giterr_set_str(C.GITERR_NET, cstr) + C.free(unsafe.Pointer(cstr)) + return -1 // we don't support anything else atm + } + + return int(callbacks.CertificateCheckCallback(&cert, valid, host)) +} + +//export packProgressCallback +func packProgressCallback(stage C.int, current, total C.uint, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + + if callbacks.PackProgressCallback == nil { + return 0 + } + + return int(callbacks.PackProgressCallback(int32(stage), uint32(current), uint32(total))) +} + +//export pushTransferProgressCallback +func pushTransferProgressCallback(current, total C.uint, bytes C.size_t, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + if callbacks.PushTransferProgressCallback == nil { + return 0 + } + + return int(callbacks.PushTransferProgressCallback(uint32(current), uint32(total), uint(bytes))) +} + +//export pushUpdateReferenceCallback +func pushUpdateReferenceCallback(refname, status *C.char, data unsafe.Pointer) int { + callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks) + + if callbacks.PushUpdateReferenceCallback == nil { + return 0 + } + + return int(callbacks.PushUpdateReferenceCallback(C.GoString(refname), C.GoString(status))) +} + +func RemoteIsValidName(name string) bool { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + if C.git_remote_is_valid_name(cname) == 1 { + return true + } + return false +} + +func (r *Remote) Free() { + runtime.SetFinalizer(r, nil) + C.git_remote_free(r.ptr) +} + +type RemoteCollection struct { + repo *Repository +} + +func (c *RemoteCollection) List() ([]string, error) { + var r C.git_strarray + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_remote_list(&r, c.repo.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + defer C.git_strarray_free(&r) + + remotes := makeStringsFromCStrings(r.strings, int(r.count)) + return remotes, nil +} + +func (c *RemoteCollection) Create(name string, url string) (*Remote, error) { + remote := &Remote{} + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create(&remote.ptr, c.repo.ptr, cname, curl) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (c *RemoteCollection) Delete(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_delete(c.repo.ptr, cname) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch string) (*Remote, error) { + remote := &Remote{} + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cfetch := C.CString(fetch) + defer C.free(unsafe.Pointer(cfetch)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create_with_fetchspec(&remote.ptr, c.repo.ptr, cname, curl, cfetch) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) { + remote := &Remote{} + + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create_anonymous(&remote.ptr, c.repo.ptr, curl) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (c *RemoteCollection) Lookup(name string) (*Remote, error) { + remote := &Remote{} + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_lookup(&remote.ptr, c.repo.ptr, cname) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (o *Remote) Name() string { + return C.GoString(C.git_remote_name(o.ptr)) +} + +func (o *Remote) Url() string { + return C.GoString(C.git_remote_url(o.ptr)) +} + +func (o *Remote) PushUrl() string { + return C.GoString(C.git_remote_pushurl(o.ptr)) +} + +func (c *RemoteCollection) SetUrl(remote, url string) error { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_url(c.repo.ptr, cremote, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (c *RemoteCollection) SetPushUrl(remote, url string) error { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_pushurl(c.repo.ptr, cremote, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (c *RemoteCollection) AddFetch(remote, refspec string) error { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_add_fetch(c.repo.ptr, cremote, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func sptr(p uintptr) *C.char { + return *(**C.char)(unsafe.Pointer(p)) +} + +func makeStringsFromCStrings(x **C.char, l int) []string { + s := make([]string, l) + i := 0 + for p := uintptr(unsafe.Pointer(x)); i < l; p += unsafe.Sizeof(uintptr(0)) { + s[i] = C.GoString(sptr(p)) + i++ + } + return s +} + +func makeCStringsFromStrings(s []string) **C.char { + l := len(s) + x := (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(unsafe.Pointer(nil)) * uintptr(l)))) + i := 0 + for p := uintptr(unsafe.Pointer(x)); i < l; p += unsafe.Sizeof(uintptr(0)) { + *(**C.char)(unsafe.Pointer(p)) = C.CString(s[i]) + i++ + } + return x +} + +func freeStrarray(arr *C.git_strarray) { + count := int(arr.count) + size := unsafe.Sizeof(unsafe.Pointer(nil)) + + i := 0 + for p := uintptr(unsafe.Pointer(arr.strings)); i < count; p += size { + C.free(unsafe.Pointer(sptr(p))) + i++ + } + + C.free(unsafe.Pointer(arr.strings)) +} + +func (o *Remote) FetchRefspecs() ([]string, error) { + crefspecs := C.git_strarray{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_get_fetch_refspecs(&crefspecs, o.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + defer C.git_strarray_free(&crefspecs) + + refspecs := makeStringsFromCStrings(crefspecs.strings, int(crefspecs.count)) + return refspecs, nil +} + +func (c *RemoteCollection) AddPush(remote, refspec string) error { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_add_push(c.repo.ptr, cremote, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) PushRefspecs() ([]string, error) { + crefspecs := C.git_strarray{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_get_push_refspecs(&crefspecs, o.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + defer C.git_strarray_free(&crefspecs) + refspecs := makeStringsFromCStrings(crefspecs.strings, int(crefspecs.count)) + return refspecs, nil +} + +func (o *Remote) RefspecCount() uint { + return uint(C.git_remote_refspec_count(o.ptr)) +} + +func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) { + C.git_fetch_init_options(options, C.GIT_FETCH_OPTIONS_VERSION) + if opts == nil { + return; + } + populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks) + options.prune = C.git_fetch_prune_t(opts.Prune) + options.update_fetchhead = cbool(opts.UpdateFetchhead) + options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags) +} + +func populatePushOptions(options *C.git_push_options, opts *PushOptions) { + C.git_push_init_options(options, C.GIT_PUSH_OPTIONS_VERSION) + if opts == nil { + return + } + + options.pb_parallelism = C.uint(opts.PbParallelism) + + populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks) +} + +// Fetch performs a fetch operation. refspecs specifies which refspecs +// to use for this fetch, use an empty list to use the refspecs from +// the configuration; msg specifies what to use for the reflog +// entries. Leave "" to use defaults. +func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error { + var cmsg *C.char = nil + if msg != "" { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + crefspecs := C.git_strarray{} + crefspecs.count = C.size_t(len(refspecs)) + crefspecs.strings = makeCStringsFromStrings(refspecs) + defer freeStrarray(&crefspecs) + + coptions := (*C.git_fetch_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_fetch_options{})))) + defer C.free(unsafe.Pointer(coptions)) + + populateFetchOptions(coptions, opts) + defer untrackCalbacksPayload(&coptions.callbacks) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_fetch(o.ptr, &crefspecs, coptions, cmsg) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks) error { + return o.Connect(ConnectDirectionFetch, callbacks) +} + +func (o *Remote) ConnectPush(callbacks *RemoteCallbacks) error { + return o.Connect(ConnectDirectionPush, callbacks) +} + +func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks) error { + var ccallbacks C.git_remote_callbacks; + populateRemoteCallbacks(&ccallbacks, callbacks) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks); ret != 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) { + + var refs **C.git_remote_head + var length C.size_t + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_remote_ls(&refs, &length, o.ptr); ret != 0 { + return nil, MakeGitError(ret) + } + + size := int(length) + + if size == 0 { + return make([]RemoteHead, 0), nil + } + + hdr := reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(refs)), + Len: size, + Cap: size, + } + + goSlice := *(*[]*C.git_remote_head)(unsafe.Pointer(&hdr)) + + var heads []RemoteHead + + for _, s := range goSlice { + head := newRemoteHeadFromC(s) + + if len(filterRefs) > 0 { + for _, r := range filterRefs { + if strings.Contains(head.Name, r) { + heads = append(heads, head) + break + } + } + } else { + heads = append(heads, head) + } + } + + return heads, nil +} + +func (o *Remote) Push(refspecs []string, opts *PushOptions) error { + crefspecs := C.git_strarray{} + crefspecs.count = C.size_t(len(refspecs)) + crefspecs.strings = makeCStringsFromStrings(refspecs) + defer freeStrarray(&crefspecs) + + coptions := (*C.git_push_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_push_options{})))) + defer C.free(unsafe.Pointer(coptions)) + + populatePushOptions(coptions, opts) + defer untrackCalbacksPayload(&coptions.callbacks) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_push(o.ptr, &crefspecs, coptions) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) PruneRefs() bool { + return C.git_remote_prune_refs(o.ptr) > 0 +} + +func (o *Remote) Prune(callbacks *RemoteCallbacks) error { + var ccallbacks C.git_remote_callbacks; + populateRemoteCallbacks(&ccallbacks, callbacks) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_prune(o.ptr, &ccallbacks) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/repository.go b/vendor/gopkg.in/libgit2/git2go.v23/repository.go new file mode 100644 index 0000000..12638e1 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/repository.go @@ -0,0 +1,456 @@ +package git + +/* +#include +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +// Repository +type Repository struct { + ptr *C.git_repository + // Remotes represents the collection of remotes and can be + // used to add, remove and configure remotes for this + // repository. + Remotes RemoteCollection + // Submodules represents the collection of submodules and can + // be used to add, remove and configure submodules in this + // repostiory. + Submodules SubmoduleCollection + // References represents the collection of references and can + // be used to create, remove or update refernces for this repository. + References ReferenceCollection + // Notes represents the collection of notes and can be used to + // read, write and delete notes from this repository. + Notes NoteCollection + // Tags represents the collection of tags and can be used to create, + // list and iterate tags in this repository. + Tags TagsCollection +} + +func newRepositoryFromC(ptr *C.git_repository) *Repository { + repo := &Repository{ptr: ptr} + + repo.Remotes.repo = repo + repo.Submodules.repo = repo + repo.References.repo = repo + repo.Notes.repo = repo + repo.Tags.repo = repo + + runtime.SetFinalizer(repo, (*Repository).Free) + + return repo +} + +func OpenRepository(path string) (*Repository, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_repository + ret := C.git_repository_open(&ptr, cpath) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newRepositoryFromC(ptr), nil +} + +func OpenRepositoryExtended(path string) (*Repository, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_repository + ret := C.git_repository_open_ext(&ptr, cpath, 0, nil) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newRepositoryFromC(ptr), nil +} + +func InitRepository(path string, isbare bool) (*Repository, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_repository + ret := C.git_repository_init(&ptr, cpath, ucbool(isbare)) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newRepositoryFromC(ptr), nil +} + +func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_repository + ret := C.git_repository_wrap_odb(&ptr, odb.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newRepositoryFromC(ptr), nil +} + +func (v *Repository) SetRefdb(refdb *Refdb) { + C.git_repository_set_refdb(v.ptr, refdb.ptr) +} + +func (v *Repository) Free() { + runtime.SetFinalizer(v, nil) + C.git_repository_free(v.ptr) +} + +func (v *Repository) Config() (*Config, error) { + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_config(&config.ptr, v.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(config, (*Config).Free) + return config, nil +} + +func (v *Repository) Index() (*Index, error) { + var ptr *C.git_index + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_index(&ptr, v.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newIndexFromC(ptr), nil +} + +func (v *Repository) lookupType(id *Oid, t ObjectType) (Object, error) { + var ptr *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_object_lookup(&ptr, v.ptr, id.toC(), C.git_otype(t)) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return allocObject(ptr, v), nil +} + +func (v *Repository) Lookup(id *Oid) (Object, error) { + return v.lookupType(id, ObjectAny) +} + +func (v *Repository) LookupTree(id *Oid) (*Tree, error) { + obj, err := v.lookupType(id, ObjectTree) + if err != nil { + return nil, err + } + + return obj.(*Tree), nil +} + +func (v *Repository) LookupCommit(id *Oid) (*Commit, error) { + obj, err := v.lookupType(id, ObjectCommit) + if err != nil { + return nil, err + } + + return obj.(*Commit), nil +} + +func (v *Repository) LookupBlob(id *Oid) (*Blob, error) { + obj, err := v.lookupType(id, ObjectBlob) + if err != nil { + return nil, err + } + + return obj.(*Blob), nil +} + +func (v *Repository) LookupTag(id *Oid) (*Tag, error) { + obj, err := v.lookupType(id, ObjectTag) + if err != nil { + return nil, err + } + + return obj.(*Tag), nil +} + +func (v *Repository) Head() (*Reference, error) { + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_repository_head(&ptr, v.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, v), nil +} + +func (v *Repository) SetHead(refname string) error { + cname := C.CString(refname) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_repository_set_head(v.ptr, cname) + if ecode != 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *Repository) SetHeadDetached(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_repository_set_head_detached(v.ptr, id.toC()) + if ecode != 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *Repository) IsHeadDetached() (bool, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_repository_head_detached(v.ptr) + if ret < 0 { + return false, MakeGitError(ret) + } + + return ret != 0, nil +} + +func (v *Repository) Walk() (*RevWalk, error) { + + var walkPtr *C.git_revwalk + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_new(&walkPtr, v.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return revWalkFromC(v, walkPtr), nil +} + +func (v *Repository) CreateCommit( + refname string, author, committer *Signature, + message string, tree *Tree, parents ...*Commit) (*Oid, error) { + + oid := new(Oid) + + var cref *C.char + if refname == "" { + cref = nil + } else { + cref = C.CString(refname) + defer C.free(unsafe.Pointer(cref)) + } + + cmsg := C.CString(message) + defer C.free(unsafe.Pointer(cmsg)) + + var cparents []*C.git_commit = nil + var parentsarg **C.git_commit = nil + + nparents := len(parents) + if nparents > 0 { + cparents = make([]*C.git_commit, nparents) + for i, v := range parents { + cparents[i] = v.cast_ptr + } + parentsarg = &cparents[0] + } + + authorSig, err := author.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(committerSig) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_commit_create( + oid.toC(), v.ptr, cref, + authorSig, committerSig, + nil, cmsg, tree.cast_ptr, C.size_t(nparents), parentsarg) + + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + +func (v *Odb) Free() { + runtime.SetFinalizer(v, nil) + C.git_odb_free(v.ptr) +} + +func (v *Refdb) Free() { + runtime.SetFinalizer(v, nil) + C.git_refdb_free(v.ptr) +} + +func (v *Repository) Odb() (odb *Odb, err error) { + odb = new(Odb) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_repository_odb(&odb.ptr, v.ptr); ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(odb, (*Odb).Free) + return odb, nil +} + +func (repo *Repository) Path() string { + return C.GoString(C.git_repository_path(repo.ptr)) +} + +func (repo *Repository) IsBare() bool { + return C.git_repository_is_bare(repo.ptr) != 0 +} + +func (repo *Repository) Workdir() string { + return C.GoString(C.git_repository_workdir(repo.ptr)) +} + +func (repo *Repository) SetWorkdir(workdir string, updateGitlink bool) error { + cstr := C.CString(workdir) + defer C.free(unsafe.Pointer(cstr)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_repository_set_workdir(repo.ptr, cstr, cbool(updateGitlink)); ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (v *Repository) TreeBuilder() (*TreeBuilder, error) { + bld := new(TreeBuilder) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_treebuilder_new(&bld.ptr, v.ptr, nil); ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(bld, (*TreeBuilder).Free) + + bld.repo = v + return bld, nil +} + +func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) { + bld := new(TreeBuilder) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_treebuilder_new(&bld.ptr, v.ptr, tree.cast_ptr); ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(bld, (*TreeBuilder).Free) + + bld.repo = v + return bld, nil +} + +type RepositoryState int + +const ( + RepositoryStateNone RepositoryState = C.GIT_REPOSITORY_STATE_NONE + RepositoryStateMerge RepositoryState = C.GIT_REPOSITORY_STATE_MERGE + RepositoryStateRevert RepositoryState = C.GIT_REPOSITORY_STATE_REVERT + RepositoryStateCherrypick RepositoryState = C.GIT_REPOSITORY_STATE_CHERRYPICK + RepositoryStateBisect RepositoryState = C.GIT_REPOSITORY_STATE_BISECT + RepositoryStateRebase RepositoryState = C.GIT_REPOSITORY_STATE_REBASE + RepositoryStateRebaseInteractive RepositoryState = C.GIT_REPOSITORY_STATE_REBASE_INTERACTIVE + RepositoryStateRebaseMerge RepositoryState = C.GIT_REPOSITORY_STATE_REBASE_MERGE + RepositoryStateApplyMailbox RepositoryState = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX + RepositoryStateApplyMailboxOrRebase RepositoryState = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE +) + +func (r *Repository) State() RepositoryState { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + return RepositoryState(C.git_repository_state(r.ptr)) +} + +func (r *Repository) StateCleanup() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cErr := C.git_repository_state_cleanup(r.ptr) + if cErr < 0 { + return MakeGitError(cErr) + } + return nil +} +func (r *Repository) AddGitIgnoreRules(rules string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + crules := C.CString(rules) + defer C.free(unsafe.Pointer(crules)) + if ret := C.git_ignore_add_rule(r.ptr, crules); ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (r *Repository) ClearGitIgnoreRules() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_ignore_clear_internal_rules(r.ptr); ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/reset.go b/vendor/gopkg.in/libgit2/git2go.v23/reset.go new file mode 100644 index 0000000..b5b7435 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/reset.go @@ -0,0 +1,26 @@ +package git + +/* +#include +*/ +import "C" +import "runtime" + +type ResetType int + +const ( + ResetSoft ResetType = C.GIT_RESET_SOFT + ResetMixed ResetType = C.GIT_RESET_MIXED + ResetHard ResetType = C.GIT_RESET_HARD +) + +func (r *Repository) ResetToCommit(commit *Commit, resetType ResetType, opts *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_reset(r.ptr, commit.gitObject.ptr, C.git_reset_t(resetType), opts.toC()) + + if ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/revparse.go b/vendor/gopkg.in/libgit2/git2go.v23/revparse.go new file mode 100644 index 0000000..7eb04f1 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/revparse.go @@ -0,0 +1,113 @@ +package git + +/* +#include + +extern void _go_git_revspec_free(git_revspec *revspec); +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type RevparseFlag int + +const ( + RevparseSingle RevparseFlag = C.GIT_REVPARSE_SINGLE + RevparseRange RevparseFlag = C.GIT_REVPARSE_RANGE + RevparseMergeBase RevparseFlag = C.GIT_REVPARSE_MERGE_BASE +) + +type Revspec struct { + to Object + from Object + flags RevparseFlag +} + +func (rs *Revspec) To() Object { + return rs.to +} + +func (rs *Revspec) From() Object { + return rs.from +} + +func (rs *Revspec) Flags() RevparseFlag { + return rs.flags +} + +func newRevspecFromC(ptr *C.git_revspec, repo *Repository) *Revspec { + var to Object + var from Object + + if ptr.to != nil { + to = allocObject(ptr.to, repo) + } + + if ptr.from != nil { + from = allocObject(ptr.from, repo) + } + + return &Revspec{ + to: to, + from: from, + flags: RevparseFlag(ptr.flags), + } +} + +func (r *Repository) Revparse(spec string) (*Revspec, error) { + cspec := C.CString(spec) + defer C.free(unsafe.Pointer(cspec)) + + var crevspec C.git_revspec + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revparse(&crevspec, r.ptr, cspec) + if ecode != 0 { + return nil, MakeGitError(ecode) + } + + return newRevspecFromC(&crevspec, r), nil +} + +func (v *Repository) RevparseSingle(spec string) (Object, error) { + cspec := C.CString(spec) + defer C.free(unsafe.Pointer(cspec)) + + var ptr *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revparse_single(&ptr, v.ptr, cspec) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return allocObject(ptr, v), nil +} + +func (r *Repository) RevparseExt(spec string) (Object, *Reference, error) { + cspec := C.CString(spec) + defer C.free(unsafe.Pointer(cspec)) + + var obj *C.git_object + var ref *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revparse_ext(&obj, &ref, r.ptr, cspec) + if ecode != 0 { + return nil, nil, MakeGitError(ecode) + } + + if ref == nil { + return allocObject(obj, r), nil, nil + } + + return allocObject(obj, r), newReferenceFromC(ref, r), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/settings.go b/vendor/gopkg.in/libgit2/git2go.v23/settings.go new file mode 100644 index 0000000..c7f1850 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/settings.go @@ -0,0 +1,102 @@ +package git + +/* +#include + +int _go_git_opts_get_search_path(int level, git_buf *buf) +{ + return git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, buf); +} + +int _go_git_opts_set_search_path(int level, const char *path) +{ + return git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, path); +} + +int _go_git_opts_set_size_t(int opt, size_t val) +{ + return git_libgit2_opts(opt, val); +} + +int _go_git_opts_get_size_t(int opt, size_t *val) +{ + return git_libgit2_opts(opt, val); +} +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +func SearchPath(level ConfigLevel) (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_opts_get_search_path(C.int(level), &buf) + if err < 0 { + return "", MakeGitError(err) + } + + return C.GoString(buf.ptr), nil +} + +func SetSearchPath(level ConfigLevel, path string) error { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_opts_set_search_path(C.int(level), cpath) + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +func MwindowSize() (int, error) { + return getSizet(C.GIT_OPT_GET_MWINDOW_SIZE) +} + +func SetMwindowSize(size int) error { + return setSizet(C.GIT_OPT_SET_MWINDOW_SIZE, size) +} + +func MwindowMappedLimit() (int, error) { + return getSizet(C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT) +} + +func SetMwindowMappedLimit(size int) error { + return setSizet(C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size) +} + +func getSizet(opt C.int) (int, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var val C.size_t + err := C._go_git_opts_get_size_t(opt, &val) + if err < 0 { + return 0, MakeGitError(err) + } + + return int(val), nil +} + +func setSizet(opt C.int, val int) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cval := C.size_t(val) + err := C._go_git_opts_set_size_t(opt, cval) + if err < 0 { + return MakeGitError(err) + } + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/signature.go b/vendor/gopkg.in/libgit2/git2go.v23/signature.go new file mode 100644 index 0000000..0518387 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/signature.go @@ -0,0 +1,73 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "time" + "unsafe" +) + +type Signature struct { + Name string + Email string + When time.Time +} + +func newSignatureFromC(sig *C.git_signature) *Signature { + // git stores minutes, go wants seconds + loc := time.FixedZone("", int(sig.when.offset)*60) + return &Signature{ + C.GoString(sig.name), + C.GoString(sig.email), + time.Unix(int64(sig.when.time), 0).In(loc), + } +} + +// the offset in mintes, which is what git wants +func (v *Signature) Offset() int { + _, offset := v.When.Zone() + return offset / 60 +} + +func (sig *Signature) toC() (*C.git_signature, error) { + if sig == nil { + return nil, nil + } + + var out *C.git_signature + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + name := C.CString(sig.Name) + defer C.free(unsafe.Pointer(name)) + + email := C.CString(sig.Email) + defer C.free(unsafe.Pointer(email)) + + ret := C.git_signature_new(&out, name, email, C.git_time_t(sig.When.Unix()), C.int(sig.Offset())) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return out, nil +} + +func (repo *Repository) DefaultSignature() (*Signature, error) { + var out *C.git_signature + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cErr := C.git_signature_default(&out, repo.ptr) + if cErr < 0 { + return nil, MakeGitError(cErr) + } + + defer C.git_signature_free(out) + + return newSignatureFromC(out), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/status.go b/vendor/gopkg.in/libgit2/git2go.v23/status.go new file mode 100644 index 0000000..068a474 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/status.go @@ -0,0 +1,179 @@ +package git + +/* +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +type Status int + +const ( + StatusCurrent Status = C.GIT_STATUS_CURRENT + StatusIndexNew Status = C.GIT_STATUS_INDEX_NEW + StatusIndexModified Status = C.GIT_STATUS_INDEX_MODIFIED + StatusIndexDeleted Status = C.GIT_STATUS_INDEX_DELETED + StatusIndexRenamed Status = C.GIT_STATUS_INDEX_RENAMED + StatusIndexTypeChange Status = C.GIT_STATUS_INDEX_TYPECHANGE + StatusWtNew Status = C.GIT_STATUS_WT_NEW + StatusWtModified Status = C.GIT_STATUS_WT_MODIFIED + StatusWtDeleted Status = C.GIT_STATUS_WT_DELETED + StatusWtTypeChange Status = C.GIT_STATUS_WT_TYPECHANGE + StatusWtRenamed Status = C.GIT_STATUS_WT_RENAMED + StatusIgnored Status = C.GIT_STATUS_IGNORED +) + +type StatusEntry struct { + Status Status + HeadToIndex DiffDelta + IndexToWorkdir DiffDelta +} + +func statusEntryFromC(statusEntry *C.git_status_entry) StatusEntry { + var headToIndex DiffDelta = DiffDelta{} + var indexToWorkdir DiffDelta = DiffDelta{} + + // Based on the libgit2 status example, head_to_index can be null in some cases + if statusEntry.head_to_index != nil { + headToIndex = diffDeltaFromC(statusEntry.head_to_index) + } + if statusEntry.index_to_workdir != nil { + indexToWorkdir = diffDeltaFromC(statusEntry.index_to_workdir) + } + + return StatusEntry{ + Status: Status(statusEntry.status), + HeadToIndex: headToIndex, + IndexToWorkdir: indexToWorkdir, + } +} + +type StatusList struct { + ptr *C.git_status_list +} + +func newStatusListFromC(ptr *C.git_status_list) *StatusList { + if ptr == nil { + return nil + } + + statusList := &StatusList{ + ptr: ptr, + } + + runtime.SetFinalizer(statusList, (*StatusList).Free) + return statusList +} + +func (statusList *StatusList) Free() { + if statusList.ptr == nil { + return + } + runtime.SetFinalizer(statusList, nil) + C.git_status_list_free(statusList.ptr) + statusList.ptr = nil +} + +func (statusList *StatusList) ByIndex(index int) (StatusEntry, error) { + if statusList.ptr == nil { + return StatusEntry{}, ErrInvalid + } + ptr := C.git_status_byindex(statusList.ptr, C.size_t(index)) + return statusEntryFromC(ptr), nil +} + +func (statusList *StatusList) EntryCount() (int, error) { + if statusList.ptr == nil { + return -1, ErrInvalid + } + return int(C.git_status_list_entrycount(statusList.ptr)), nil +} + +type StatusOpt int + +const ( + StatusOptIncludeUntracked StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNTRACKED + StatusOptIncludeIgnored StatusOpt = C.GIT_STATUS_OPT_INCLUDE_IGNORED + StatusOptIncludeUnmodified StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNMODIFIED + StatusOptExcludeSubmodules StatusOpt = C.GIT_STATUS_OPT_EXCLUDE_SUBMODULES + StatusOptRecurseUntrackedDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS + StatusOptDisablePathspecMatch StatusOpt = C.GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH + StatusOptRecurseIgnoredDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_IGNORED_DIRS + StatusOptRenamesHeadToIndex StatusOpt = C.GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX + StatusOptRenamesIndexToWorkdir StatusOpt = C.GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR + StatusOptSortCaseSensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_SENSITIVELY + StatusOptSortCaseInsensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY + StatusOptRenamesFromRewrites StatusOpt = C.GIT_STATUS_OPT_RENAMES_FROM_REWRITES + StatusOptNoRefresh StatusOpt = C.GIT_STATUS_OPT_NO_REFRESH + StatusOptUpdateIndex StatusOpt = C.GIT_STATUS_OPT_UPDATE_INDEX +) + +type StatusShow int + +const ( + StatusShowIndexAndWorkdir StatusShow = C.GIT_STATUS_SHOW_INDEX_AND_WORKDIR + StatusShowIndexOnly StatusShow = C.GIT_STATUS_SHOW_INDEX_ONLY + StatusShowWorkdirOnly StatusShow = C.GIT_STATUS_SHOW_WORKDIR_ONLY +) + +type StatusOptions struct { + Show StatusShow + Flags StatusOpt + Pathspec []string +} + +func (v *Repository) StatusList(opts *StatusOptions) (*StatusList, error) { + var ptr *C.git_status_list + var copts *C.git_status_options + + if opts != nil { + cpathspec := C.git_strarray{} + if opts.Pathspec != nil { + cpathspec.count = C.size_t(len(opts.Pathspec)) + cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) + defer freeStrarray(&cpathspec) + } + + copts = &C.git_status_options{ + version: C.GIT_STATUS_OPTIONS_VERSION, + show: C.git_status_show_t(opts.Show), + flags: C.uint(opts.Flags), + pathspec: cpathspec, + } + } else { + copts = &C.git_status_options{} + ret := C.git_status_init_options(copts, C.GIT_STATUS_OPTIONS_VERSION) + if ret < 0 { + return nil, MakeGitError(ret) + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_status_list_new(&ptr, v.ptr, copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newStatusListFromC(ptr), nil +} + +func (v *Repository) StatusFile(path string) (Status, error) { + var statusFlags C.uint + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_status_file(&statusFlags, v.ptr, cPath) + if ret < 0 { + return 0, MakeGitError(ret) + } + return Status(statusFlags), nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/submodule.go b/vendor/gopkg.in/libgit2/git2go.v23/submodule.go new file mode 100644 index 0000000..4a32ce4 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/submodule.go @@ -0,0 +1,342 @@ +package git + +/* +#include + +extern int _go_git_visit_submodule(git_repository *repo, void *fct); +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +// SubmoduleUpdateOptions +type SubmoduleUpdateOptions struct { + *CheckoutOpts + *FetchOptions + CloneCheckoutStrategy CheckoutStrategy +} + +// Submodule +type Submodule struct { + ptr *C.git_submodule +} + +type SubmoduleUpdate int + +const ( + SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT + SubmoduleUpdateRebase SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE + SubmoduleUpdateMerge SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE + SubmoduleUpdateNone SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_NONE +) + +type SubmoduleIgnore int + +const ( + SubmoduleIgnoreNone SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_NONE + SubmoduleIgnoreUntracked SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_UNTRACKED + SubmoduleIgnoreDirty SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DIRTY + SubmoduleIgnoreAll SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_ALL +) + +type SubmoduleStatus int + +const ( + SubmoduleStatusInHead SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_HEAD + SubmoduleStatusInIndex SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_INDEX + SubmoduleStatusInConfig SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_CONFIG + SubmoduleStatusInWd SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_WD + SubmoduleStatusIndexAdded SubmoduleStatus = C.GIT_SUBMODULE_STATUS_INDEX_ADDED + SubmoduleStatusIndexDeleted SubmoduleStatus = C.GIT_SUBMODULE_STATUS_INDEX_DELETED + SubmoduleStatusIndexModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_INDEX_MODIFIED + SubmoduleStatusWdUninitialized SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_UNINITIALIZED + SubmoduleStatusWdAdded SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_ADDED + SubmoduleStatusWdDeleted SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_DELETED + SubmoduleStatusWdModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_MODIFIED + SubmoduleStatusWdIndexModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED + SubmoduleStatusWdWdModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_WD_MODIFIED + SubmoduleStatusWdUntracked SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_UNTRACKED +) + +type SubmoduleRecurse int + +const ( + SubmoduleRecurseNo SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_NO + SubmoduleRecurseYes SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_YES + SubmoduleRecurseOndemand SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_ONDEMAND +) + +type SubmoduleCollection struct { + repo *Repository +} + +func SubmoduleStatusIsUnmodified(status int) bool { + o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex | + SubmoduleStatusInConfig | SubmoduleStatusInWd) + return o == 0 +} + +func (c *SubmoduleCollection) Lookup(name string) (*Submodule, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + sub := new(Submodule) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_lookup(&sub.ptr, c.repo.ptr, cname) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return sub, nil +} + +type SubmoduleCbk func(sub *Submodule, name string) int + +//export SubmoduleVisitor +func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer) C.int { + sub := &Submodule{(*C.git_submodule)(csub)} + + if callback, ok := pointerHandles.Get(handle).(SubmoduleCbk); ok { + return (C.int)(callback(sub, C.GoString(name))) + } else { + panic("invalid submodule visitor callback") + } +} + +func (c *SubmoduleCollection) Foreach(cbk SubmoduleCbk) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + handle := pointerHandles.Track(cbk) + defer pointerHandles.Untrack(handle) + + ret := C._go_git_visit_submodule(c.repo.ptr, handle) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (c *SubmoduleCollection) Add(url, path string, use_git_link bool) (*Submodule, error) { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + sub := new(Submodule) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_add_setup(&sub.ptr, c.repo.ptr, curl, cpath, cbool(use_git_link)) + if ret < 0 { + return nil, MakeGitError(ret) + } + return sub, nil +} + +func (sub *Submodule) FinalizeAdd() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_add_finalize(sub.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (sub *Submodule) AddToIndex(write_index bool) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_add_to_index(sub.ptr, cbool(write_index)) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (sub *Submodule) Name() string { + n := C.git_submodule_name(sub.ptr) + return C.GoString(n) +} + +func (sub *Submodule) Path() string { + n := C.git_submodule_path(sub.ptr) + return C.GoString(n) +} + +func (sub *Submodule) Url() string { + n := C.git_submodule_url(sub.ptr) + return C.GoString(n) +} + +func (c *SubmoduleCollection) SetUrl(submodule, url string) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_url(c.repo.ptr, csubmodule, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (sub *Submodule) IndexId() *Oid { + idx := C.git_submodule_index_id(sub.ptr) + if idx == nil { + return nil + } + return newOidFromC(idx) +} + +func (sub *Submodule) HeadId() *Oid { + idx := C.git_submodule_head_id(sub.ptr) + if idx == nil { + return nil + } + return newOidFromC(idx) +} + +func (sub *Submodule) WdId() *Oid { + idx := C.git_submodule_wd_id(sub.ptr) + if idx == nil { + return nil + } + return newOidFromC(idx) +} + +func (sub *Submodule) Ignore() SubmoduleIgnore { + o := C.git_submodule_ignore(sub.ptr) + return SubmoduleIgnore(o) +} + +func (c *SubmoduleCollection) SetIgnore(submodule string, ignore SubmoduleIgnore) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_ignore(c.repo.ptr, csubmodule, C.git_submodule_ignore_t(ignore)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (sub *Submodule) UpdateStrategy() SubmoduleUpdate { + o := C.git_submodule_update_strategy(sub.ptr) + return SubmoduleUpdate(o) +} + +func (c *SubmoduleCollection) SetUpdate(submodule string, update SubmoduleUpdate) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_update(c.repo.ptr, csubmodule, C.git_submodule_update_t(update)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse { + return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)) +} + +func (c *SubmoduleCollection) SetFetchRecurseSubmodules(submodule string, recurse SubmoduleRecurse) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_fetch_recurse_submodules(c.repo.ptr, csubmodule, C.git_submodule_recurse_t(recurse)) + if ret < 0 { + return MakeGitError(C.int(ret)) + } + return nil +} + +func (sub *Submodule) Init(overwrite bool) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_init(sub.ptr, cbool(overwrite)) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (sub *Submodule) Sync() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_sync(sub.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (sub *Submodule) Open() (*Repository, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_repository + ret := C.git_submodule_open(&ptr, sub.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newRepositoryFromC(ptr), nil +} + +func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error { + var copts C.git_submodule_update_options + err := populateSubmoduleUpdateOptions(&copts, opts) + if err != nil { + return err + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_update(sub.ptr, cbool(init), &copts) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *SubmoduleUpdateOptions) error { + C.git_submodule_update_init_options(ptr, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION) + + if opts == nil { + return nil + } + + populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) + populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions) + ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy) + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/tag.go b/vendor/gopkg.in/libgit2/git2go.v23/tag.go new file mode 100644 index 0000000..ca85156 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/tag.go @@ -0,0 +1,211 @@ +package git + +/* +#include + +extern int _go_git_tag_foreach(git_repository *repo, void *payload); +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +// Tag +type Tag struct { + gitObject + cast_ptr *C.git_tag +} + +func (t Tag) Message() string { + return C.GoString(C.git_tag_message(t.cast_ptr)) +} + +func (t Tag) Name() string { + return C.GoString(C.git_tag_name(t.cast_ptr)) +} + +func (t Tag) Tagger() *Signature { + cast_ptr := C.git_tag_tagger(t.cast_ptr) + return newSignatureFromC(cast_ptr) +} + +func (t Tag) Target() Object { + var ptr *C.git_object + ret := C.git_tag_target(&ptr, t.cast_ptr) + + if ret != 0 { + return nil + } + + return allocObject(ptr, t.repo) +} + +func (t Tag) TargetId() *Oid { + return newOidFromC(C.git_tag_target_id(t.cast_ptr)) +} + +func (t Tag) TargetType() ObjectType { + return ObjectType(C.git_tag_target_type(t.cast_ptr)) +} + +type TagsCollection struct { + repo *Repository +} + +func (c *TagsCollection) Create( + name string, commit *Commit, tagger *Signature, message string) (*Oid, error) { + + oid := new(Oid) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + cmessage := C.CString(message) + defer C.free(unsafe.Pointer(cmessage)) + + taggerSig, err := tagger.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(taggerSig) + + ctarget := commit.gitObject.ptr + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, ctarget, taggerSig, cmessage, 0) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + +// CreateLightweight creates a new lightweight tag pointing to a commit +// and returns the id of the target object. +// +// The name of the tag is validated for consistency (see git_tag_create() for the rules +// https://libgit2.github.com/libgit2/#HEAD/group/tag/git_tag_create) and should +// not conflict with an already existing tag name. +// +// If force is true and a reference already exists with the given name, it'll be replaced. +// +// The created tag is a simple reference and can be queried using +// repo.References.Lookup("refs/tags/"). The name of the tag (eg "v1.0.0") +// is queried with ref.Shorthand(). +func (c *TagsCollection) CreateLightweight(name string, commit *Commit, force bool) (*Oid, error) { + + oid := new(Oid) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + ctarget := commit.gitObject.ptr + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, ctarget, cbool(force)) + if err < 0 { + return nil, MakeGitError(err) + } + + return oid, nil +} + +// List returns the names of all the tags in the repository, +// eg: ["v1.0.1", "v2.0.0"]. +func (c *TagsCollection) List() ([]string, error) { + var strC C.git_strarray + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_tag_list(&strC, c.repo.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + defer C.git_strarray_free(&strC) + + tags := makeStringsFromCStrings(strC.strings, int(strC.count)) + return tags, nil +} + +// ListWithMatch returns the names of all the tags in the repository +// that match a given pattern. +// +// The pattern is a standard fnmatch(3) pattern http://man7.org/linux/man-pages/man3/fnmatch.3.html +func (c *TagsCollection) ListWithMatch(pattern string) ([]string, error) { + var strC C.git_strarray + + patternC := C.CString(pattern) + defer C.free(unsafe.Pointer(patternC)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_tag_list_match(&strC, patternC, c.repo.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + defer C.git_strarray_free(&strC) + + tags := makeStringsFromCStrings(strC.strings, int(strC.count)) + return tags, nil +} + +// TagForeachCallback is called for each tag in the repository. +// +// The name is the full ref name eg: "refs/tags/v1.0.0". +// +// Note that the callback is called for lightweight tags as well, +// so repo.LookupTag() will return an error for these tags. Use +// repo.References.Lookup() instead. +type TagForeachCallback func(name string, id *Oid) error +type tagForeachData struct { + callback TagForeachCallback + err error +} + +//export gitTagForeachCb +func gitTagForeachCb(name *C.char, id *C.git_oid, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*tagForeachData) + if !ok { + panic("could not retrieve tag foreach CB handle") + } + + err := data.callback(C.GoString(name), newOidFromC(id)) + if err != nil { + data.err = err + return C.GIT_EUSER + } + + return 0 +} + +// Foreach calls the callback for each tag in the repository. +func (c *TagsCollection) Foreach(callback TagForeachCallback) error { + data := tagForeachData{ + callback: callback, + err: nil, + } + + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_tag_foreach(c.repo.ptr, handle) + if err == C.GIT_EUSER { + return data.err + } + if err < 0 { + return MakeGitError(err) + } + + return nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/tree.go b/vendor/gopkg.in/libgit2/git2go.v23/tree.go new file mode 100644 index 0000000..f543c11 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/tree.go @@ -0,0 +1,195 @@ +package git + +/* +#include + +extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr); +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +type Filemode int + +const ( + FilemodeTree Filemode = C.GIT_FILEMODE_TREE + FilemodeBlob Filemode = C.GIT_FILEMODE_BLOB + FilemodeBlobExecutable Filemode = C.GIT_FILEMODE_BLOB_EXECUTABLE + FilemodeLink Filemode = C.GIT_FILEMODE_LINK + FilemodeCommit Filemode = C.GIT_FILEMODE_COMMIT +) + +type Tree struct { + gitObject + cast_ptr *C.git_tree +} + +type TreeEntry struct { + Name string + Id *Oid + Type ObjectType + Filemode Filemode +} + +func newTreeEntry(entry *C.git_tree_entry) *TreeEntry { + return &TreeEntry{ + C.GoString(C.git_tree_entry_name(entry)), + newOidFromC(C.git_tree_entry_id(entry)), + ObjectType(C.git_tree_entry_type(entry)), + Filemode(C.git_tree_entry_filemode(entry)), + } +} + +func (t Tree) EntryByName(filename string) *TreeEntry { + cname := C.CString(filename) + defer C.free(unsafe.Pointer(cname)) + + entry := C.git_tree_entry_byname(t.cast_ptr, cname) + if entry == nil { + return nil + } + + return newTreeEntry(entry) +} + +// EntryById performs a lookup for a tree entry with the given SHA value. +// +// It returns a *TreeEntry that is owned by the Tree. You don't have to +// free it, but you must not use it after the Tree is freed. +// +// Warning: this must examine every entry in the tree, so it is not fast. +func (t Tree) EntryById(id *Oid) *TreeEntry { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + entry := C.git_tree_entry_byid(t.cast_ptr, id.toC()) + if entry == nil { + return nil + } + + return newTreeEntry(entry) +} + +// EntryByPath looks up an entry by its full path, recursing into +// deeper trees if necessary (i.e. if there are slashes in the path) +func (t Tree) EntryByPath(path string) (*TreeEntry, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + var entry *C.git_tree_entry + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_tree_entry_bypath(&entry, t.cast_ptr, cpath) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newTreeEntry(entry), nil +} + +func (t Tree) EntryByIndex(index uint64) *TreeEntry { + entry := C.git_tree_entry_byindex(t.cast_ptr, C.size_t(index)) + if entry == nil { + return nil + } + + return newTreeEntry(entry) +} + +func (t Tree) EntryCount() uint64 { + num := C.git_tree_entrycount(t.cast_ptr) + return uint64(num) +} + +type TreeWalkCallback func(string, *TreeEntry) int + +//export CallbackGitTreeWalk +func CallbackGitTreeWalk(_root *C.char, _entry unsafe.Pointer, ptr unsafe.Pointer) C.int { + root := C.GoString(_root) + entry := (*C.git_tree_entry)(_entry) + + if callback, ok := pointerHandles.Get(ptr).(TreeWalkCallback); ok { + return C.int(callback(root, newTreeEntry(entry))) + } else { + panic("invalid treewalk callback") + } +} + +func (t Tree) Walk(callback TreeWalkCallback) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ptr := pointerHandles.Track(callback) + defer pointerHandles.Untrack(ptr) + + err := C._go_git_treewalk( + t.cast_ptr, + C.GIT_TREEWALK_PRE, + ptr, + ) + + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +type TreeBuilder struct { + ptr *C.git_treebuilder + repo *Repository +} + +func (v *TreeBuilder) Free() { + runtime.SetFinalizer(v, nil) + C.git_treebuilder_free(v.ptr) +} + +func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) error { + cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_treebuilder_insert(nil, v.ptr, cfilename, id.toC(), C.git_filemode_t(filemode)) + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +func (v *TreeBuilder) Remove(filename string) error { + cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_treebuilder_remove(v.ptr, cfilename) + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +func (v *TreeBuilder) Write() (*Oid, error) { + oid := new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_treebuilder_write(oid.toC(), v.ptr) + + if err < 0 { + return nil, MakeGitError(err) + } + + return oid, nil +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/walk.go b/vendor/gopkg.in/libgit2/git2go.v23/walk.go new file mode 100644 index 0000000..60e618d --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/walk.go @@ -0,0 +1,209 @@ +package git + +/* +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +// RevWalk + +type SortType uint + +const ( + SortNone SortType = C.GIT_SORT_NONE + SortTopological SortType = C.GIT_SORT_TOPOLOGICAL + SortTime SortType = C.GIT_SORT_TIME + SortReverse SortType = C.GIT_SORT_REVERSE +) + +type RevWalk struct { + ptr *C.git_revwalk + repo *Repository +} + +func revWalkFromC(repo *Repository, c *C.git_revwalk) *RevWalk { + v := &RevWalk{ptr: c, repo: repo} + runtime.SetFinalizer(v, (*RevWalk).Free) + return v +} + +func (v *RevWalk) Reset() { + C.git_revwalk_reset(v.ptr) +} + +func (v *RevWalk) Push(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_push(v.ptr, id.toC()) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushGlob(glob string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_push_glob(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushRange(r string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(r) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_push_range(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushRef(r string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(r) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_push_ref(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushHead() (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_push_head(v.ptr) + if ecode < 0 { + err = MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) Hide(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_hide(v.ptr, id.toC()) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) HideGlob(glob string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_hide_glob(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) HideRef(r string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(r) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_hide_ref(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) HideHead() (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_hide_head(v.ptr) + if ecode < 0 { + err = MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) Next(id *Oid) (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_revwalk_next(id.toC(), v.ptr) + switch { + case ret < 0: + err = MakeGitError(ret) + } + + return +} + +type RevWalkIterator func(commit *Commit) bool + +func (v *RevWalk) Iterate(fun RevWalkIterator) (err error) { + oid := new(Oid) + for { + err = v.Next(oid) + if IsErrorCode(err, ErrIterOver) { + return nil + } + if err != nil { + if err.(GitError).Code == ErrIterOver { + err = nil + } + + return err + } + + commit, err := v.repo.LookupCommit(oid) + if err != nil { + return err + } + + cont := fun(commit) + if !cont { + break + } + } + + return nil +} + +func (v *RevWalk) SimplifyFirstParent() { + C.git_revwalk_simplify_first_parent(v.ptr) +} + +func (v *RevWalk) Sorting(sm SortType) { + C.git_revwalk_sorting(v.ptr, C.uint(sm)) +} + +func (v *RevWalk) Free() { + + runtime.SetFinalizer(v, nil) + C.git_revwalk_free(v.ptr) +} diff --git a/vendor/gopkg.in/libgit2/git2go.v23/wrapper.c b/vendor/gopkg.in/libgit2/git2go.v23/wrapper.c new file mode 100644 index 0000000..a0688c0 --- /dev/null +++ b/vendor/gopkg.in/libgit2/git2go.v23/wrapper.c @@ -0,0 +1,167 @@ +#include "_cgo_export.h" +#include +#include +#include + +typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); + +void _go_git_populate_remote_cb(git_clone_options *opts) +{ + opts->remote_cb = (git_remote_create_cb)remoteCreateCallback; +} + +int _go_git_visit_submodule(git_repository *repo, void *fct) +{ + return git_submodule_foreach(repo, (gogit_submodule_cbk)&SubmoduleVisitor, fct); +} + +int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr) +{ + return git_tree_walk(tree, mode, (git_treewalk_cb)&CallbackGitTreeWalk, ptr); +} + +int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload) +{ + return git_packbuilder_foreach(pb, (git_packbuilder_foreach_cb)&packbuilderForEachCb, payload); +} + +int _go_git_odb_foreach(git_odb *db, void *payload) +{ + return git_odb_foreach(db, (git_odb_foreach_cb)&odbForEachCb, payload); +} + +void _go_git_odb_backend_free(git_odb_backend *backend) +{ + if (backend->free) + backend->free(backend); + + return; +} + +void _go_git_refdb_backend_free(git_refdb_backend *backend) +{ + if (backend->free) + backend->free(backend); + + return; +} + +int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload) +{ + git_diff_file_cb fcb = NULL; + git_diff_hunk_cb hcb = NULL; + git_diff_line_cb lcb = NULL; + + if (eachFile) { + fcb = (git_diff_file_cb)&diffForEachFileCb; + } + + if (eachHunk) { + hcb = (git_diff_hunk_cb)&diffForEachHunkCb; + } + + if (eachLine) { + lcb = (git_diff_line_cb)&diffForEachLineCb; + } + + return git_diff_foreach(diff, fcb, NULL, hcb, lcb, payload); +} + +int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const char *new_path, git_diff_options *opts, int eachFile, int eachHunk, int eachLine, void *payload) +{ + git_diff_file_cb fcb = NULL; + git_diff_hunk_cb hcb = NULL; + git_diff_line_cb lcb = NULL; + + if (eachFile) { + fcb = (git_diff_file_cb)&diffForEachFileCb; + } + + if (eachHunk) { + hcb = (git_diff_hunk_cb)&diffForEachHunkCb; + } + + if (eachLine) { + lcb = (git_diff_line_cb)&diffForEachLineCb; + } + + return git_diff_blobs(old, old_path, new, new_path, opts, fcb, NULL, hcb, lcb, payload); +} + +void _go_git_setup_diff_notify_callbacks(git_diff_options *opts) { + opts->notify_cb = (git_diff_notify_cb)diffNotifyCb; +} + +void _go_git_setup_callbacks(git_remote_callbacks *callbacks) { + typedef int (*completion_cb)(git_remote_completion_type type, void *data); + typedef int (*update_tips_cb)(const char *refname, const git_oid *a, const git_oid *b, void *data); + typedef int (*push_update_reference_cb)(const char *refname, const char *status, void *data); + + callbacks->sideband_progress = (git_transport_message_cb)sidebandProgressCallback; + callbacks->completion = (completion_cb)completionCallback; + callbacks->credentials = (git_cred_acquire_cb)credentialsCallback; + callbacks->transfer_progress = (git_transfer_progress_cb)transferProgressCallback; + callbacks->update_tips = (update_tips_cb)updateTipsCallback; + callbacks->certificate_check = (git_transport_certificate_check_cb) certificateCheckCallback; + callbacks->pack_progress = (git_packbuilder_progress) packProgressCallback; + callbacks->push_transfer_progress = (git_push_transfer_progress) pushTransferProgressCallback; + callbacks->push_update_reference = (push_update_reference_cb) pushUpdateReferenceCallback; +} + +int _go_blob_chunk_cb(char *buffer, size_t maxLen, void *payload) +{ + return blobChunkCb(buffer, maxLen, payload); +} + +int _go_git_blob_create_fromchunks(git_oid *id, + git_repository *repo, + const char *hintpath, + void *payload) +{ + return git_blob_create_fromchunks(id, repo, hintpath, _go_blob_chunk_cb, payload); +} + +int _go_git_index_add_all(git_index *index, const git_strarray *pathspec, unsigned int flags, void *callback) { + git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL; + return git_index_add_all(index, pathspec, flags, cb, callback); +} + +int _go_git_index_update_all(git_index *index, const git_strarray *pathspec, void *callback) { + git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL; + return git_index_update_all(index, pathspec, cb, callback); +} + +int _go_git_index_remove_all(git_index *index, const git_strarray *pathspec, void *callback) { + git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL; + return git_index_remove_all(index, pathspec, cb, callback); +} + +int _go_git_tag_foreach(git_repository *repo, void *payload) +{ + return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload); +} + +int _go_git_merge_file(git_merge_file_result* out, char* ancestorContents, size_t ancestorLen, char* ancestorPath, unsigned int ancestorMode, char* oursContents, size_t oursLen, char* oursPath, unsigned int oursMode, char* theirsContents, size_t theirsLen, char* theirsPath, unsigned int theirsMode, git_merge_file_options* copts) { + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_input ours = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_input theirs = GIT_MERGE_FILE_INPUT_INIT; + + ancestor.ptr = ancestorContents; + ancestor.size = ancestorLen; + ancestor.path = ancestorPath; + ancestor.mode = ancestorMode; + + ours.ptr = oursContents; + ours.size = oursLen; + ours.path = oursPath; + ours.mode = oursMode; + + theirs.ptr = theirsContents; + theirs.size = theirsLen; + theirs.path = theirsPath; + theirs.mode = theirsMode; + + return git_merge_file(out, &ancestor, &ours, &theirs, copts); +} + +/* EOF */ From 608da106532ffbd1ee557d70283a9bc96405bbe6 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 16:47:12 -0700 Subject: [PATCH 04/79] fix libgit2 compilation --- hack/build-libgit2.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hack/build-libgit2.sh b/hack/build-libgit2.sh index 9a07a00..f7f0f21 100755 --- a/hack/build-libgit2.sh +++ b/hack/build-libgit2.sh @@ -7,7 +7,8 @@ VENDORED_PATH=vendor/libgit2 cd $VENDORED_PATH && mkdir -p install/lib && mkdir -p build && -cd build && -cmake . && +cmake -DTHREADSAFE=ON \ + -DBUILD_CLAR=OFF \ + . && make && sudo make install From c99d54b9a43c9d8c9a1c300a0852dc019e1ce718 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 17:08:48 -0700 Subject: [PATCH 05/79] used packaged libgit2 --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6b8af6f..e6aefa1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,14 @@ language: go sudo: required +dist: trusty go: - 1.6 +addons: + apt: + packages: + - libgit2-dev + go_import_path: rsprd.com/spread services: @@ -11,7 +17,6 @@ services: install: # install build dependencies - make deps - - make build-libgit2 before_script: - make validate From 5a2b4a3605572013dabc117b096eb35356f46376 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 17:19:39 -0700 Subject: [PATCH 06/79] andddd back to building --- .travis.yml | 7 +------ hack/build-libgit2.sh | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6aefa1..6b8af6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,8 @@ language: go sudo: required -dist: trusty go: - 1.6 -addons: - apt: - packages: - - libgit2-dev - go_import_path: rsprd.com/spread services: @@ -17,6 +11,7 @@ services: install: # install build dependencies - make deps + - make build-libgit2 before_script: - make validate diff --git a/hack/build-libgit2.sh b/hack/build-libgit2.sh index f7f0f21..1548897 100755 --- a/hack/build-libgit2.sh +++ b/hack/build-libgit2.sh @@ -10,5 +10,4 @@ mkdir -p build && cmake -DTHREADSAFE=ON \ -DBUILD_CLAR=OFF \ . && -make && -sudo make install +cmake --build . --target install From 09095328e119410a0be24f88cec535a5f2503e80 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 14 Jun 2016 17:25:11 -0700 Subject: [PATCH 07/79] use sudo when installing libgit2 --- hack/build-libgit2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/build-libgit2.sh b/hack/build-libgit2.sh index 1548897..0c054f9 100755 --- a/hack/build-libgit2.sh +++ b/hack/build-libgit2.sh @@ -10,4 +10,4 @@ mkdir -p build && cmake -DTHREADSAFE=ON \ -DBUILD_CLAR=OFF \ . && -cmake --build . --target install +sudo cmake --build . --target install From 11ea08d29bf93d9e7147aeb09dd17d7f7fe095ea Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 15 Jun 2016 10:01:13 -0700 Subject: [PATCH 08/79] adding library search path --- Makefile | 6 +++--- hack/build-libgit2.sh | 7 ++++--- test/mattermost-demo.sh | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index f526e0b..e502e2b 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ validate: lint checkgofmt vet .PHONY: build build: build/spread -build/spread: +build/spread: vendor/libgit2/build/libgit2.pc $(GO) build $(GOBUILD_FLAGS) -ldflags "$(GOBUILD_LDFLAGS)" -o $@ $(EXEC_PKG) build/spread-linux-static: @@ -86,8 +86,8 @@ build-gitlab: build/spread-linux-static cp ./build/spread-linux-static $(GITLAB_CONTEXT) $(DOCKER) build $(DOCKER_OPTS) -t $(GITLAB_IMAGE_NAME) $(GITLAB_CONTEXT) -build-libgit2: vendor/libgit2/build/libgit2.a -vendor/libgit2/build/libgit2.a: vendor/libgit2 +build-libgit2: vendor/libgit2/build/libgit2.pc +vendor/libgit2/build/libgit2.pc: vendor/libgit2 ./hack/build-libgit2.sh vendor/libgit2: vendor/libgit2.tar.gz diff --git a/hack/build-libgit2.sh b/hack/build-libgit2.sh index 0c054f9..5d2e326 100755 --- a/hack/build-libgit2.sh +++ b/hack/build-libgit2.sh @@ -5,9 +5,10 @@ set -ex VENDORED_PATH=vendor/libgit2 cd $VENDORED_PATH && -mkdir -p install/lib && +mkdir -p _install && mkdir -p build && +cd build && cmake -DTHREADSAFE=ON \ -DBUILD_CLAR=OFF \ - . && -sudo cmake --build . --target install + .. && +cmake --build . diff --git a/test/mattermost-demo.sh b/test/mattermost-demo.sh index 74aa8dc..48c0567 100755 --- a/test/mattermost-demo.sh +++ b/test/mattermost-demo.sh @@ -1,6 +1,9 @@ #!/bin/bash set -e +PROJECT=${GOPATH}/src/rsprd.com/spread +export LD_LIBRARY_PATH=${PROJECT}/vendor/libgit2/build + NODE_IP="127.0.0.1" SLEEP_TIME=10 LOCALKUBE_TAG="v1.2.1-v1" @@ -23,9 +26,9 @@ function retry() { return 1 } -KUBECTL="./build/kubectl" -MATTERMOST="./build/mattermost" -export PATH="$(pwd)/build:$PATH" +KUBECTL="${PROJECT}/build/kubectl" +MATTERMOST="${PROJECT}/build/mattermost" +export PATH="${PROJECT}/build:$PATH" if [ ! -f $KUBECTL ]; then echo "Installing kubectl..." From 6193056cf35209d30cf2cd00efd4c2fa4e081106 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 15 Jun 2016 12:26:04 -0700 Subject: [PATCH 09/79] dynamically compiling --- .travis.yml | 4 +++- Makefile | 31 ++++++++++++++++++++++++++----- test/mattermost-demo.sh | 1 - 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b8af6f..b71fd92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ sudo: required go: - 1.6 +env: LD_LIBRARY_PATH=/usr/local/lib + go_import_path: rsprd.com/spread services: @@ -11,7 +13,7 @@ services: install: # install build dependencies - make deps - - make build-libgit2 + - make install-libgit2 before_script: - make validate diff --git a/Makefile b/Makefile index e502e2b..9bea546 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ BASE := rsprd.com/spread +DIR := $(GOPATH)/src/$(BASE) CMD_NAME := spread EXEC_PKG := $(BASE)/cmd/$(CMD_NAME) @@ -20,14 +21,31 @@ GOFMT ?= gofmt "-s" GOLINT ?= golint DOCKER ?= docker +GOFILES := find . -name '*.go' -not -path "./vendor/*" + VERSION_LDFLAG := -X main.Version=$(SPREAD_VERSION) -GOFILES := find . -name '*.go' -not -path "./vendor/*" +LIBGIT2_DIR := $(DIR)/vendor/libgit2 +LIBGIT2_BUILD := $(LIBGIT2_DIR)/build +LIBGIT2_PKGCONFIG := $(LIBGIT2_BUILD)/libgit2.pc + +LIBGIT2_CGOFLAG := -I$(LIBGIT2_DIR)/include + +# Do not use sudo on OS X +ifneq ($(OS),Windows_NT) + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + SUDO := + else + SUDO := sudo + endif +endif GOBUILD_LDFLAGS ?= $(VERSION_LDFLAG) GOBUILD_FLAGS ?= -i -v GOTEST_FLAGS ?= -v GOX_FLAGS ?= -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" -os="${GOX_OS}" -arch="${GOX_ARCH}" +CGO_ENV ?= CGO_CFLAGS="$(LIBGIT2_CGOFLAG)" STATIC_LDFLAGS ?= -extldflags "-static" --s -w @@ -68,8 +86,8 @@ validate: lint checkgofmt vet .PHONY: build build: build/spread -build/spread: vendor/libgit2/build/libgit2.pc - $(GO) build $(GOBUILD_FLAGS) -ldflags "$(GOBUILD_LDFLAGS)" -o $@ $(EXEC_PKG) +build/spread: + $(CGO_ENV) $(GO) build $(GOBUILD_FLAGS) -ldflags "$(GOBUILD_LDFLAGS)" -o $@ $(EXEC_PKG) build/spread-linux-static: GOOS=linux $(GO) build -o $@ $(GOBUILD_FLAGS) -ldflags "$(GOBUILD_LDFLAGS) $(STATIC_LDFLAGS)" $(EXEC_PKG) @@ -86,8 +104,11 @@ build-gitlab: build/spread-linux-static cp ./build/spread-linux-static $(GITLAB_CONTEXT) $(DOCKER) build $(DOCKER_OPTS) -t $(GITLAB_IMAGE_NAME) $(GITLAB_CONTEXT) -build-libgit2: vendor/libgit2/build/libgit2.pc -vendor/libgit2/build/libgit2.pc: vendor/libgit2 +install-libgit2: build-libgit2 + cd $(LIBGIT2_BUILD) && $(SUDO) make install + +build-libgit2: $(LIBGIT2_PKGCONFIG) +$(LIBGIT2_PKGCONFIG): vendor/libgit2 ./hack/build-libgit2.sh vendor/libgit2: vendor/libgit2.tar.gz diff --git a/test/mattermost-demo.sh b/test/mattermost-demo.sh index 48c0567..ab10e7f 100755 --- a/test/mattermost-demo.sh +++ b/test/mattermost-demo.sh @@ -2,7 +2,6 @@ set -e PROJECT=${GOPATH}/src/rsprd.com/spread -export LD_LIBRARY_PATH=${PROJECT}/vendor/libgit2/build NODE_IP="127.0.0.1" SLEEP_TIME=10 From e3ac3afee358c1b51626c7748690dd32ee9ede46 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 15 Jun 2016 17:19:33 -0700 Subject: [PATCH 10/79] more time for integration test --- .travis.yml | 31 ++++++++++++++++--------------- test/mattermost-demo.sh | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index b71fd92..21eac74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,18 +21,19 @@ before_script: script: - make test -after_success: - # If release then crosscompile - - if [ -n "$TRAVIS_TAG" ]; then SPREAD_VERSION=$TRAVIS_TAG make crossbuild; else echo "Skipping release build..."; fi - -deploy: - provider: releases - api_key: - secure: Jqzgb1hVOHhXAtzr1CaYtz9Ws8VGbyTP5qdWr9Dobg69hCTQDLg8Yuc/pJBIMtajxiQnrNmcI5ZpSdrPd1mxUKQBxr6ra3A8G/Utnfs8ilO0qDR4/pF+G+dVZlIUT/8iAfvpEiCkEckndrB/fVgR1p3o4R7kceHsjduTy+dX0j1v9jN1FA901o2C6tz3OYGn85q7p0wYXOd6l4WhtRK2EShvcPMG7Hs5Vz9Ihb7cznH1Lxpst2zohXz45O0vzfyFLXJp4RftAvg/1IOl9E4OTIanL9SQckRxfFRI57AAogKdJPYrbm8L7/HxRN96lW5e8EtNZXf77Orc/9NvR4YENRUINDhaCxz+IUTtU2TLt4nyCtHCEGyqFJ4ZnK5+sO4Ha7STiPVLNZSfu6rZPo3l77o0JTLo/IK8QtTrQ6H6JyjN3brle/FoOJTxtbjZ/DZKpR4QweO2zG4sQ8sKS8KuQ4LUx9Gm4ucuI8vsWIUsSMg8mKkCCQzf2z84Ih5FxZEwzXrAH+xVrrmUQlsgF3KflT/78LnJMFhOoxO+CPW2sStxndxh2L8duLOt+Jrf4URUnrjwGyWhebPcTI/gj0wuRUFY3VahkmWAD4r/3A/62s1/mc2vYwT9VLRxtKsA16Ur5ujM4nUVfJG/nYVE07+rqrngd9OCvmM8u5WmFpMqKWc= - skip_cleanup: true - file: - - ./build/spread_linux_amd64 - - ./build/spread_darwin_amd64 - - ./build/spread_windows_amd64.exe - on: - tags: true +# Disable cross compilation until we get static libgit2 cross compilation working +#after_success: +# # If release then crosscompile +# - if [ -n "$TRAVIS_TAG" ]; then SPREAD_VERSION=$TRAVIS_TAG make crossbuild; else echo "Skipping release build..."; fi +# +#deploy: +# provider: releases +# api_key: +# secure: Jqzgb1hVOHhXAtzr1CaYtz9Ws8VGbyTP5qdWr9Dobg69hCTQDLg8Yuc/pJBIMtajxiQnrNmcI5ZpSdrPd1mxUKQBxr6ra3A8G/Utnfs8ilO0qDR4/pF+G+dVZlIUT/8iAfvpEiCkEckndrB/fVgR1p3o4R7kceHsjduTy+dX0j1v9jN1FA901o2C6tz3OYGn85q7p0wYXOd6l4WhtRK2EShvcPMG7Hs5Vz9Ihb7cznH1Lxpst2zohXz45O0vzfyFLXJp4RftAvg/1IOl9E4OTIanL9SQckRxfFRI57AAogKdJPYrbm8L7/HxRN96lW5e8EtNZXf77Orc/9NvR4YENRUINDhaCxz+IUTtU2TLt4nyCtHCEGyqFJ4ZnK5+sO4Ha7STiPVLNZSfu6rZPo3l77o0JTLo/IK8QtTrQ6H6JyjN3brle/FoOJTxtbjZ/DZKpR4QweO2zG4sQ8sKS8KuQ4LUx9Gm4ucuI8vsWIUsSMg8mKkCCQzf2z84Ih5FxZEwzXrAH+xVrrmUQlsgF3KflT/78LnJMFhOoxO+CPW2sStxndxh2L8duLOt+Jrf4URUnrjwGyWhebPcTI/gj0wuRUFY3VahkmWAD4r/3A/62s1/mc2vYwT9VLRxtKsA16Ur5ujM4nUVfJG/nYVE07+rqrngd9OCvmM8u5WmFpMqKWc= +# skip_cleanup: true +# file: +# - ./build/spread_linux_amd64 +# - ./build/spread_darwin_amd64 +# - ./build/spread_windows_amd64.exe +# on: +# tags: true diff --git a/test/mattermost-demo.sh b/test/mattermost-demo.sh index ab10e7f..3fd866d 100755 --- a/test/mattermost-demo.sh +++ b/test/mattermost-demo.sh @@ -56,4 +56,4 @@ NODE_PORT=$(kubectl get services/mattermost-app --template='{{range .spec.ports} echo "Checking if started app successfully" echo "waiting up to 100 seconds" -retry "curl --fail http://$NODE_IP:$NODE_PORT" "10" +retry "curl --fail http://$NODE_IP:$NODE_PORT" "15" From 9df7def0d3217b551cbf0210bda4abe9fec62bca Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 02:34:20 -0700 Subject: [PATCH 11/79] moved init logic into new project package and added unit tests --- cli/build.go | 1 - cli/cli.go | 8 ---- cli/init.go | 41 ++++---------------- pkg/project/project.go | 64 +++++++++++++++++++++++++++++++ pkg/project/project_test.go | 76 +++++++++++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 43 deletions(-) create mode 100644 pkg/project/project.go create mode 100644 pkg/project/project_test.go diff --git a/cli/build.go b/cli/build.go index ad387dd..6ab0c87 100644 --- a/cli/build.go +++ b/cli/build.go @@ -27,7 +27,6 @@ func (s SpreadCli) Build() *cli.Command { e, err := input.Build() if err != nil { - println("build") s.fatalf(inputError(srcDir, err)) } diff --git a/cli/cli.go b/cli/cli.go index 18fb555..f43d843 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -7,14 +7,6 @@ import ( "strings" ) -const ( - // SpreadDirectory is the name of the directory that holds a Spread repository. - SpreadDirectory = ".spread" - - // GitDirectory is the name of the directory holding the bare Git repository within the SpreadDirectory. - GitDirectory = "git" -) - // SpreadCli is the spread command line client. type SpreadCli struct { // input stream (ie. stdin) diff --git a/cli/init.go b/cli/init.go index a704192..d6c9760 100644 --- a/cli/init.go +++ b/cli/init.go @@ -1,13 +1,9 @@ package cli import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" + "rsprd.com/spread/pkg/project" "github.com/codegangsta/cli" - git "gopkg.in/libgit2/git2go.v23" ) // Init sets up a Spread repository for versioning. @@ -17,40 +13,17 @@ func (s SpreadCli) Init() *cli.Command { Usage: "spread init ", Description: "Create a new spread repository in the given directory. If none is given, the working directory will be used.", Action: func(c *cli.Context) { - target := SpreadDirectory - // Check if path is specified - if len(c.Args().First()) != 0 { - target = c.Args().First() + target := c.Args().First() + if len(target) == 0 { + target = project.SpreadDirectory } - // Get absolute path to directory - target, err := filepath.Abs(target) + proj, err := project.InitProject(target) if err != nil { - s.fatalf("Could not resolve '%s': %v", target, err) + s.fatalf("Could not create Spread project: %v", err) } - // Check if directory exists - if _, err = os.Stat(target); err == nil { - s.fatalf("Could not initialize repo: '%s' already exists", target) - } else if !os.IsNotExist(err) { - s.fatalf("Could not stat repo directory: %v", err) - } - - // Create .spread directory in target directory - if err = os.MkdirAll(target, 0755); err != nil { - s.fatalf("Could not create repo directory: %v", err) - } - - // Create bare Git repository in .spread directory with the directory name "git" - gitDir := filepath.Join(target, GitDirectory) - if _, err = git.InitRepository(gitDir, true); err != nil { - s.fatalf("Could not create Object repository: %v", err) - } - - // Create .gitignore file in directory ignoring Git repository - ignoreName := filepath.Join(SpreadDirectory, ".gitignore") - ignoreData := fmt.Sprintf("/%s", GitDirectory) - ioutil.WriteFile(ignoreName, []byte(ignoreData), 0755) + s.printf("Created Spread repository in %s.", proj.Path) }, } } diff --git a/pkg/project/project.go b/pkg/project/project.go new file mode 100644 index 0000000..5be7bb6 --- /dev/null +++ b/pkg/project/project.go @@ -0,0 +1,64 @@ +package project + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + git "gopkg.in/libgit2/git2go.v23" +) + +const ( + // SpreadDirectory is the name of the directory that holds a Spread repository. + SpreadDirectory = ".spread" + + // GitDirectory is the name of the directory holding the bare Git repository within the SpreadDirectory. + GitDirectory = "git" +) + +type Project struct { + Path string +} + +// InitProject creates a new Spread project including initializing a Git repository on disk. +// A target must be specified. +func InitProject(target string) (*Project, error) { + // Check if path is specified + if len(target) == 0 { + return nil, errors.New("target must be specified") + } + + // Get absolute path to directory + target, err := filepath.Abs(target) + if err != nil { + return nil, fmt.Errorf("could not resolve '%s': %v", target, err) + } + + // Check if directory exists + if _, err = os.Stat(target); err == nil { + return nil, fmt.Errorf("'%s' already exists", target) + } else if !os.IsNotExist(err) { + return nil, err + } + + // Create .spread directory in target directory + if err = os.MkdirAll(target, 0755); err != nil { + return nil, fmt.Errorf("could not create repo directory: %v", err) + } + + // Create bare Git repository in .spread directory with the directory name "git" + gitDir := filepath.Join(target, GitDirectory) + if _, err = git.InitRepository(gitDir, true); err != nil { + return nil, fmt.Errorf("Could not create Object repository: %v", err) + } + + // Create .gitignore file in directory ignoring Git repository + ignoreName := filepath.Join(target, ".gitignore") + ignoreData := fmt.Sprintf("/%s", GitDirectory) + ioutil.WriteFile(ignoreName, []byte(ignoreData), 0755) + return &Project{ + Path: target, + }, nil +} \ No newline at end of file diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go new file mode 100644 index 0000000..58c7cae --- /dev/null +++ b/pkg/project/project_test.go @@ -0,0 +1,76 @@ +package project + +import ( + "os" + "testing" + "path/filepath" + + "github.com/stretchr/testify/assert" + git "gopkg.in/libgit2/git2go.v23" +) + +const ( + testDir = "../../test/repos/" +) + +func init() { + os.RemoveAll(testDir) +} + +func TestInitProjectNoPath(t *testing.T) { + target := "" + proj, err := InitProject(target) + assert.Error(t, err, "projects require paths") + assert.Nil(t, proj, "invalid projects should not be returned") +} + +func TestInitProjectRelative(t *testing.T) { + target := filepath.Join(testDir, "relativeTest") + //defer os.RemoveAll(target) + + proj, err := InitProject(target) + assert.NoError(t, err) + assert.NotNil(t, proj) + + checkProject(t, proj.Path) +} + +func TestInitProjectAbsolute(t *testing.T) { + target := filepath.Join(testDir, "absoluteTest") + target, err := filepath.Abs(target) + assert.NoError(t, err) + + //defer os.RemoveAll(target) + + proj, err := InitProject(target) + assert.NoError(t, err) + assert.NotNil(t, proj) + + checkProject(t, proj.Path) +} + +func checkProject(t *testing.T, target string) { + // check that project directory was created + checkPath(t, target, true) + + // check .gitignore + ignorePath := filepath.Join(target, ".gitignore") + checkPath(t, ignorePath, false) + + // check git directory + gitDir := filepath.Join(target, GitDirectory) + checkPath(t, gitDir, true) + repo, err := git.OpenRepository(gitDir) + assert.NoError(t, err) + + assert.True(t, filepath.Clean(repo.Path()) == gitDir) +} + +// checkPath determines if a path has been created. +// The test errors if it doesn't exist or doesn't match dir. +func checkPath(t *testing.T, target string, dir bool) { + fileInfo, err := os.Stat(target) + if assert.NoError(t, err) { + assert.True(t, fileInfo.IsDir() == dir, "should have created directory at %s", target) + } +} \ No newline at end of file From 76cf6c4e8bfe24c8b4b46245e1d709e7cc1b38f0 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 04:58:09 -0700 Subject: [PATCH 12/79] began work on object indexing --- pkg/data/fields.go | 108 ++++++++++ pkg/data/object.go | 48 +++++ pkg/project/project.go | 43 +++- pkg/project/project_test.go | 4 +- pkg/project/repo.go | 46 ++++ pkg/spreadproto/object.pb.go | 398 +++++++++++++++++++++++++++++++++++ 6 files changed, 642 insertions(+), 5 deletions(-) create mode 100644 pkg/data/fields.go create mode 100644 pkg/data/object.go create mode 100644 pkg/project/repo.go create mode 100644 pkg/spreadproto/object.pb.go diff --git a/pkg/data/fields.go b/pkg/data/fields.go new file mode 100644 index 0000000..f1ca230 --- /dev/null +++ b/pkg/data/fields.go @@ -0,0 +1,108 @@ +package data + +import ( + "fmt" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +func buildField(key string, data interface{}) (*pb.Field, error) { + if data == nil { + return buildNil(key) + } + + switch typedData := data.(type) { + case bool: + return buildBool(key, typedData) + case float64: + return buildNumber(key, typedData) + case string: + return buildString(key, typedData) + case []interface{}: + return buildArray(key, typedData) + case map[string]interface{}: + return buildMap(key, typedData) + } + + return nil, fmt.Errorf("could not resolve type of %s (value=%+v)", key, data) +} + +func buildNil(key string) (*pb.Field, error) { + return &pb.Field{ + Key: key, + Value: &pb.FieldValue{ + Type: pb.FieldValue_NULL, + }, + }, nil +} + +func buildBool(key string, data bool) (*pb.Field, error) { + return &pb.Field{ + Key: key, + Value: &pb.FieldValue{ + Type: pb.FieldValue_BOOL, + Value: fmt.Sprintf("%t", data), + }, + }, nil +} + +func buildNumber(key string, data float64) (*pb.Field, error) { + return &pb.Field{ + Key: key, + Value: &pb.FieldValue{ + Type: pb.FieldValue_NUMBER, + Value: fmt.Sprintf("%g", data), + }, + }, nil +} + +func buildString(key string, data string) (*pb.Field, error) { + return &pb.Field{ + Key: key, + Value: &pb.FieldValue{ + Type: pb.FieldValue_STRING, + Value: data, + }, + }, nil +} + +func buildArray(key string, data []interface{}) (*pb.Field, error) { + arr := make([]*pb.Field, len(data)) + for k, v := range data { + kStr := fmt.Sprintf("%d", k) + field, err := buildField(kStr, v) + if err != nil { + return nil, err + } + arr[k] = field + } + + return &pb.Field{ + Key: key, + Value: &pb.FieldValue{ + Type: pb.FieldValue_ARRAY, + }, + Fields: arr, + }, nil +} + +func buildMap(key string, data map[string]interface{}) (*pb.Field, error) { + arr := make([]*pb.Field, len(data)) + i := 0 + for k, v := range data { + field, err := buildField(k, v) + if err != nil { + return nil, err + } + arr[i] = field + i++ + } + + return &pb.Field{ + Key: key, + Value: &pb.FieldValue{ + Type: pb.FieldValue_MAP, + }, + Fields: arr, + }, nil +} diff --git a/pkg/data/object.go b/pkg/data/object.go new file mode 100644 index 0000000..ce70861 --- /dev/null +++ b/pkg/data/object.go @@ -0,0 +1,48 @@ +package data + +import ( + "encoding/json" + "fmt" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +// CreateObject uses reflection to convert the data (usually a struct) into an Object. +func CreateObject(name, path string, ptr interface{}) (*pb.Object, error) { + data, err := json.Marshal(ptr) + if err != nil { + return nil, fmt.Errorf("unable to generate JSON object: %v", err) + } + + // this is a bit hacky but not sure of a better way to ensure proper tagging + var jsonData map[string]interface{} + err = json.Unmarshal(data, &jsonData) + if err != nil { + return nil, err + } + + return CreateObjectFromMap(name, path, jsonData) +} + +// CreateObjectFromMap creates an Object, using the entries of a map as fields. +// This supports maps embedded as values. It is assumed that types are limited to JSON types. +func CreateObjectFromMap(name, path string, data map[string]interface{}) (*pb.Object, error) { + obj := &pb.Object{ + Name: name, + Info: &pb.ObjectInfo{ + Path: path, + }, + } + + i := 0 + obj.Fields = make([]*pb.Field, len(data)) + for k, v := range data { + field, err := buildField(k, v) + if err != nil { + return nil, err + } + obj.Fields[i] = field + i++ + } + return obj, nil +} diff --git a/pkg/project/project.go b/pkg/project/project.go index 5be7bb6..2503f64 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -20,6 +20,7 @@ const ( type Project struct { Path string + repo *git.Repository } // InitProject creates a new Spread project including initializing a Git repository on disk. @@ -27,7 +28,7 @@ type Project struct { func InitProject(target string) (*Project, error) { // Check if path is specified if len(target) == 0 { - return nil, errors.New("target must be specified") + return nil, ErrEmptyPath } // Get absolute path to directory @@ -50,7 +51,8 @@ func InitProject(target string) (*Project, error) { // Create bare Git repository in .spread directory with the directory name "git" gitDir := filepath.Join(target, GitDirectory) - if _, err = git.InitRepository(gitDir, true); err != nil { + repo, err := git.InitRepository(gitDir, true) + if err != nil { return nil, fmt.Errorf("Could not create Object repository: %v", err) } @@ -60,5 +62,40 @@ func InitProject(target string) (*Project, error) { ioutil.WriteFile(ignoreName, []byte(ignoreData), 0755) return &Project{ Path: target, + repo: repo, }, nil -} \ No newline at end of file +} + +// OpenProject attempts to open the project at the given path. +func OpenProject(target string) (*Project, error) { + // Check if path is specified + if len(target) == 0 { + return nil, ErrEmptyPath + } + + // check that path exists and is dir + if fileInfo, err := os.Stat(target); err != nil { + return nil, err + } else if !fileInfo.IsDir() { + return nil, ErrPathNotDir + } + + gitDir := filepath.Join(target, GitDirectory) + repo, err := git.OpenRepository(gitDir) + if err != nil { + return nil, fmt.Errorf("failed to open Git repository: %v", err) + } + + return &Project{ + Path: target, + repo: repo, + }, nil +} + +var ( + // ErrEmptyPath is returned when a target string is empty. + ErrEmptyPath = errors.New("path must be specified") + + // ErrPathNotDir is returned when a target is a file and is expected to be a directory. + ErrPathNotDir = errors.New("a directory must be specified") +) diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index 58c7cae..e077c0b 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -2,8 +2,8 @@ package project import ( "os" - "testing" "path/filepath" + "testing" "github.com/stretchr/testify/assert" git "gopkg.in/libgit2/git2go.v23" @@ -73,4 +73,4 @@ func checkPath(t *testing.T, target string, dir bool) { if assert.NoError(t, err) { assert.True(t, fileInfo.IsDir() == dir, "should have created directory at %s", target) } -} \ No newline at end of file +} diff --git a/pkg/project/repo.go b/pkg/project/repo.go new file mode 100644 index 0000000..a69cf40 --- /dev/null +++ b/pkg/project/repo.go @@ -0,0 +1,46 @@ +package project + +import ( + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + git "gopkg.in/libgit2/git2go.v23" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +func (p *Project) AddObjectToIndex(obj *pb.Object) error { + info := obj.GetInfo() + if info == nil { + return ErrNilObjectInfo + } + + data, err := proto.Marshal(obj) + if err != nil { + return fmt.Errorf("could not encode object: %v", err) + } + + oid, err := p.repo.CreateBlobFromBuffer(data) + if err != nil { + return fmt.Errorf("could not write Object as blob in Git repo: %v", err) + } + + entry := &git.IndexEntry{ + Mode: git.FilemodeBlob, + Size: uint32(len(data)), + Id: oid, + Path: info.Path, + } + + index, err := p.repo.Index() + if err != nil { + return fmt.Errorf("could not retreive index: %v", err) + } + + return index.Add(entry) +} + +var ( + ErrNilObjectInfo = errors.New("an object's Info field cannot be nil") +) diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go new file mode 100644 index 0000000..609e264 --- /dev/null +++ b/pkg/spreadproto/object.pb.go @@ -0,0 +1,398 @@ +// Code generated by protoc-gen-go. +// source: object.proto +// DO NOT EDIT! + +package spreadproto + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Delta_Status int32 + +const ( + Delta_ADDED Delta_Status = 0 + Delta_DELETED Delta_Status = 1 + Delta_MODIFIED Delta_Status = 2 + Delta_RENAMED Delta_Status = 3 + Delta_COPIED Delta_Status = 4 + Delta_IGNORED Delta_Status = 5 + Delta_UNTRACKED Delta_Status = 6 +) + +var Delta_Status_name = map[int32]string{ + 0: "ADDED", + 1: "DELETED", + 2: "MODIFIED", + 3: "RENAMED", + 4: "COPIED", + 5: "IGNORED", + 6: "UNTRACKED", +} +var Delta_Status_value = map[string]int32{ + "ADDED": 0, + "DELETED": 1, + "MODIFIED": 2, + "RENAMED": 3, + "COPIED": 4, + "IGNORED": 5, + "UNTRACKED": 6, +} + +func (x Delta_Status) String() string { + return proto.EnumName(Delta_Status_name, int32(x)) +} +func (Delta_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{0, 0} } + +type DiffField_Status int32 + +const ( + DiffField_ADDED DiffField_Status = 0 + DiffField_CHANGED DiffField_Status = 1 + DiffField_REMOVED DiffField_Status = 2 +) + +var DiffField_Status_name = map[int32]string{ + 0: "ADDED", + 1: "CHANGED", + 2: "REMOVED", +} +var DiffField_Status_value = map[string]int32{ + "ADDED": 0, + "CHANGED": 1, + "REMOVED": 2, +} + +func (x DiffField_Status) String() string { + return proto.EnumName(DiffField_Status_name, int32(x)) +} +func (DiffField_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{3, 0} } + +type FieldValue_Type int32 + +const ( + FieldValue_NUMBER FieldValue_Type = 0 + FieldValue_STRING FieldValue_Type = 1 + FieldValue_BOOL FieldValue_Type = 2 + FieldValue_MAP FieldValue_Type = 3 + FieldValue_ARRAY FieldValue_Type = 4 + FieldValue_NULL FieldValue_Type = 5 +) + +var FieldValue_Type_name = map[int32]string{ + 0: "NUMBER", + 1: "STRING", + 2: "BOOL", + 3: "MAP", + 4: "ARRAY", + 5: "NULL", +} +var FieldValue_Type_value = map[string]int32{ + "NUMBER": 0, + "STRING": 1, + "BOOL": 2, + "MAP": 3, + "ARRAY": 4, + "NULL": 5, +} + +func (x FieldValue_Type) String() string { + return proto.EnumName(FieldValue_Type_name, int32(x)) +} +func (FieldValue_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{6, 0} } + +// Delta is the difference between two objects. +type Delta struct { + Status Delta_Status `protobuf:"varint,1,opt,name=status,enum=spread.Delta_Status" json:"status,omitempty"` + Summary *DeltaSummary `protobuf:"bytes,2,opt,name=summary" json:"summary,omitempty"` + OldName string `protobuf:"bytes,3,opt,name=oldName" json:"oldName,omitempty"` + NewName string `protobuf:"bytes,4,opt,name=newName" json:"newName,omitempty"` + Fields []*DiffField `protobuf:"bytes,5,rep,name=fields" json:"fields,omitempty"` +} + +func (m *Delta) Reset() { *m = Delta{} } +func (m *Delta) String() string { return proto.CompactTextString(m) } +func (*Delta) ProtoMessage() {} +func (*Delta) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } + +func (m *Delta) GetSummary() *DeltaSummary { + if m != nil { + return m.Summary + } + return nil +} + +func (m *Delta) GetFields() []*DiffField { + if m != nil { + return m.Fields + } + return nil +} + +// DeltaSummary provides a summary of changes in a delta. +type DeltaSummary struct { + Added int64 `protobuf:"varint,1,opt,name=added" json:"added,omitempty"` + Changed int64 `protobuf:"varint,2,opt,name=changed" json:"changed,omitempty"` + Removed int64 `protobuf:"varint,3,opt,name=removed" json:"removed,omitempty"` +} + +func (m *DeltaSummary) Reset() { *m = DeltaSummary{} } +func (m *DeltaSummary) String() string { return proto.CompactTextString(m) } +func (*DeltaSummary) ProtoMessage() {} +func (*DeltaSummary) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} } + +// Diff represents the difference between two sets of objects. +type Diff struct { + Stats *DiffStats `protobuf:"bytes,1,opt,name=stats" json:"stats,omitempty"` + Deltas []*Delta `protobuf:"bytes,2,rep,name=deltas" json:"deltas,omitempty"` +} + +func (m *Diff) Reset() { *m = Diff{} } +func (m *Diff) String() string { return proto.CompactTextString(m) } +func (*Diff) ProtoMessage() {} +func (*Diff) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} } + +func (m *Diff) GetStats() *DiffStats { + if m != nil { + return m.Stats + } + return nil +} + +func (m *Diff) GetDeltas() []*Delta { + if m != nil { + return m.Deltas + } + return nil +} + +// DiffField represents the difference between two fields. +type DiffField struct { + Status DiffField_Status `protobuf:"varint,1,opt,name=status,enum=spread.DiffField_Status" json:"status,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` + OldValue *FieldValue `protobuf:"bytes,3,opt,name=oldValue" json:"oldValue,omitempty"` + NewValue *FieldValue `protobuf:"bytes,4,opt,name=newValue" json:"newValue,omitempty"` + OldLink *Link `protobuf:"bytes,5,opt,name=oldLink" json:"oldLink,omitempty"` + NewLink *Link `protobuf:"bytes,6,opt,name=newLink" json:"newLink,omitempty"` + LinkChanged bool `protobuf:"varint,7,opt,name=linkChanged" json:"linkChanged,omitempty"` + Children []*DiffField `protobuf:"bytes,8,rep,name=children" json:"children,omitempty"` +} + +func (m *DiffField) Reset() { *m = DiffField{} } +func (m *DiffField) String() string { return proto.CompactTextString(m) } +func (*DiffField) ProtoMessage() {} +func (*DiffField) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} } + +func (m *DiffField) GetOldValue() *FieldValue { + if m != nil { + return m.OldValue + } + return nil +} + +func (m *DiffField) GetNewValue() *FieldValue { + if m != nil { + return m.NewValue + } + return nil +} + +func (m *DiffField) GetOldLink() *Link { + if m != nil { + return m.OldLink + } + return nil +} + +func (m *DiffField) GetNewLink() *Link { + if m != nil { + return m.NewLink + } + return nil +} + +func (m *DiffField) GetChildren() []*DiffField { + if m != nil { + return m.Children + } + return nil +} + +// DiffStats provides a summary of the operations in a diff. +type DiffStats struct { + ObjectsChanged int64 `protobuf:"varint,1,opt,name=objectsChanged" json:"objectsChanged,omitempty"` + Insertions int64 `protobuf:"varint,2,opt,name=insertions" json:"insertions,omitempty"` + Deletions int64 `protobuf:"varint,3,opt,name=deletions" json:"deletions,omitempty"` + Renames int64 `protobuf:"varint,4,opt,name=renames" json:"renames,omitempty"` +} + +func (m *DiffStats) Reset() { *m = DiffStats{} } +func (m *DiffStats) String() string { return proto.CompactTextString(m) } +func (*DiffStats) ProtoMessage() {} +func (*DiffStats) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{4} } + +// Field represents a field of an object. +type Field struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Value *FieldValue `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` + Link *Link `protobuf:"bytes,3,opt,name=link" json:"link,omitempty"` + Fields []*Field `protobuf:"bytes,4,rep,name=fields" json:"fields,omitempty"` +} + +func (m *Field) Reset() { *m = Field{} } +func (m *Field) String() string { return proto.CompactTextString(m) } +func (*Field) ProtoMessage() {} +func (*Field) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} } + +func (m *Field) GetValue() *FieldValue { + if m != nil { + return m.Value + } + return nil +} + +func (m *Field) GetLink() *Link { + if m != nil { + return m.Link + } + return nil +} + +func (m *Field) GetFields() []*Field { + if m != nil { + return m.Fields + } + return nil +} + +// FieldValue represents the value of a field. +type FieldValue struct { + Type FieldValue_Type `protobuf:"varint,1,opt,name=type,enum=spread.FieldValue_Type" json:"type,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` + Link string `protobuf:"bytes,3,opt,name=link" json:"link,omitempty"` +} + +func (m *FieldValue) Reset() { *m = FieldValue{} } +func (m *FieldValue) String() string { return proto.CompactTextString(m) } +func (*FieldValue) ProtoMessage() {} +func (*FieldValue) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{6} } + +// Link represents a relationship to another field. +type Link struct { + // fullIRI is the full representation on an IRI. An IRI is the JSON-LD equivalent of an URL. This string contains the information found the in the following fields. + FullIRI string `protobuf:"bytes,1,opt,name=fullIRI" json:"fullIRI,omitempty"` + // repo is the Redspread repository being linked to + Repo string `protobuf:"bytes,2,opt,name=repo" json:"repo,omitempty"` + // oid is the Git object id of the commit or tree being linked to + Oid string `protobuf:"bytes,3,opt,name=oid" json:"oid,omitempty"` + // path is full name of the object within a repository + Path string `protobuf:"bytes,4,opt,name=path" json:"path,omitempty"` +} + +func (m *Link) Reset() { *m = Link{} } +func (m *Link) String() string { return proto.CompactTextString(m) } +func (*Link) ProtoMessage() {} +func (*Link) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} } + +// Object holds a representation of a Kubernetes object. +type Object struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Info *ObjectInfo `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` + Fields []*Field `protobuf:"bytes,3,rep,name=fields" json:"fields,omitempty"` +} + +func (m *Object) Reset() { *m = Object{} } +func (m *Object) String() string { return proto.CompactTextString(m) } +func (*Object) ProtoMessage() {} +func (*Object) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} } + +func (m *Object) GetInfo() *ObjectInfo { + if m != nil { + return m.Info + } + return nil +} + +func (m *Object) GetFields() []*Field { + if m != nil { + return m.Fields + } + return nil +} + +// ObjectInfo provides metadata about an object. +type ObjectInfo struct { + Path string `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"` +} + +func (m *ObjectInfo) Reset() { *m = ObjectInfo{} } +func (m *ObjectInfo) String() string { return proto.CompactTextString(m) } +func (*ObjectInfo) ProtoMessage() {} +func (*ObjectInfo) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} } + +func init() { + proto.RegisterType((*Delta)(nil), "spread.Delta") + proto.RegisterType((*DeltaSummary)(nil), "spread.DeltaSummary") + proto.RegisterType((*Diff)(nil), "spread.Diff") + proto.RegisterType((*DiffField)(nil), "spread.DiffField") + proto.RegisterType((*DiffStats)(nil), "spread.DiffStats") + proto.RegisterType((*Field)(nil), "spread.Field") + proto.RegisterType((*FieldValue)(nil), "spread.FieldValue") + proto.RegisterType((*Link)(nil), "spread.Link") + proto.RegisterType((*Object)(nil), "spread.Object") + proto.RegisterType((*ObjectInfo)(nil), "spread.ObjectInfo") + proto.RegisterEnum("spread.Delta_Status", Delta_Status_name, Delta_Status_value) + proto.RegisterEnum("spread.DiffField_Status", DiffField_Status_name, DiffField_Status_value) + proto.RegisterEnum("spread.FieldValue_Type", FieldValue_Type_name, FieldValue_Type_value) +} + +var fileDescriptor2 = []byte{ + // 661 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x54, 0xdb, 0x6e, 0xd3, 0x40, + 0x10, 0xc5, 0xf1, 0x25, 0xf6, 0x38, 0x29, 0xed, 0x82, 0xc0, 0xaa, 0x54, 0x29, 0x18, 0x2a, 0xf5, + 0x85, 0x3c, 0x94, 0x0f, 0x00, 0x37, 0x4e, 0x83, 0x45, 0x62, 0x57, 0x6e, 0x5a, 0x04, 0x3c, 0xb9, + 0xf1, 0x86, 0x9a, 0x3a, 0x76, 0x64, 0x3b, 0x45, 0xfd, 0x13, 0x7e, 0x84, 0xdf, 0x43, 0xcc, 0xee, + 0xda, 0xbd, 0xa4, 0xcd, 0x93, 0xb3, 0x73, 0xce, 0xce, 0xec, 0x9c, 0x33, 0x13, 0xe8, 0xe4, 0x17, + 0xbf, 0xe8, 0xac, 0xea, 0x2f, 0x8b, 0xbc, 0xca, 0x89, 0x56, 0x2e, 0x0b, 0x1a, 0xc5, 0xf6, 0x3f, + 0x09, 0x54, 0x97, 0xa6, 0x55, 0x44, 0xde, 0x81, 0x56, 0x56, 0x51, 0xb5, 0x2a, 0x2d, 0xa9, 0x27, + 0x1d, 0x6c, 0x1d, 0xbe, 0xec, 0x0b, 0x4a, 0x9f, 0xc3, 0xfd, 0x53, 0x8e, 0x91, 0x7d, 0x68, 0x97, + 0xab, 0xc5, 0x22, 0x2a, 0x6e, 0xac, 0x16, 0xd2, 0xcc, 0x35, 0xda, 0xa9, 0xc0, 0xc8, 0x73, 0x68, + 0xe7, 0x69, 0xec, 0x47, 0x0b, 0x6a, 0xc9, 0x48, 0x33, 0x58, 0x20, 0xa3, 0xbf, 0x79, 0x40, 0xe1, + 0x81, 0x37, 0xa0, 0xcd, 0x13, 0x9a, 0xc6, 0xa5, 0xa5, 0xf6, 0x64, 0xcc, 0xb3, 0x73, 0x9b, 0x27, + 0x99, 0xcf, 0x8f, 0x19, 0x62, 0xcf, 0x40, 0xab, 0xab, 0x1a, 0xa0, 0x3a, 0xae, 0x3b, 0x74, 0xb7, + 0x9f, 0x11, 0x13, 0xda, 0xee, 0x70, 0x3c, 0x9c, 0xe2, 0x41, 0x22, 0x1d, 0xd0, 0x27, 0x81, 0xeb, + 0x1d, 0x7b, 0x78, 0x6a, 0x31, 0x28, 0x1c, 0xfa, 0xce, 0x04, 0x0f, 0x32, 0x01, 0xd0, 0x06, 0xc1, + 0x09, 0x03, 0x14, 0x06, 0x78, 0x23, 0x3f, 0x08, 0xf1, 0xa0, 0x92, 0x2e, 0x18, 0x67, 0xfe, 0x34, + 0x74, 0x06, 0x5f, 0xf0, 0xa8, 0xd9, 0x1f, 0xa1, 0xf3, 0xe0, 0xe5, 0x5d, 0x50, 0xa3, 0x38, 0xa6, + 0x31, 0x57, 0x41, 0x66, 0xef, 0x9e, 0x5d, 0x46, 0xd9, 0x4f, 0x0c, 0xb4, 0x9a, 0x40, 0x41, 0x17, + 0xf9, 0x35, 0x06, 0x58, 0x67, 0xb2, 0x3d, 0x02, 0x85, 0x3d, 0x99, 0xf4, 0x40, 0x65, 0xfa, 0x09, + 0xf9, 0xd6, 0xfa, 0x61, 0x6d, 0x94, 0x64, 0x0f, 0xb4, 0x98, 0x95, 0x2a, 0x31, 0x15, 0x6b, 0xb9, + 0xfb, 0x40, 0x3a, 0xfb, 0x6f, 0x0b, 0x8c, 0xdb, 0xe6, 0xc9, 0xc1, 0x9a, 0x1d, 0xd6, 0x23, 0x7d, + 0x1a, 0x4b, 0x4c, 0x90, 0xaf, 0xa8, 0xb0, 0xc3, 0x40, 0x17, 0x75, 0x14, 0xfe, 0x3c, 0x4a, 0x57, + 0x42, 0x79, 0xf3, 0x90, 0x34, 0x17, 0xf9, 0x25, 0x8e, 0x30, 0x16, 0xba, 0x21, 0x58, 0xca, 0x46, + 0xd6, 0x1e, 0x37, 0x71, 0x9c, 0x64, 0x57, 0xe8, 0x11, 0x23, 0x75, 0x1a, 0x12, 0x8b, 0x31, 0x18, + 0x93, 0x70, 0x58, 0x7b, 0x02, 0x7e, 0x01, 0x66, 0x8a, 0xdf, 0x41, 0xad, 0x5e, 0x1b, 0x29, 0x3a, + 0x79, 0x0b, 0xfa, 0xec, 0x32, 0x49, 0xe3, 0x82, 0x66, 0x96, 0xbe, 0xc9, 0xf7, 0xf7, 0x1b, 0x7c, + 0x1f, 0x7c, 0x76, 0xfc, 0x11, 0xf7, 0x9d, 0x3b, 0x3d, 0x09, 0xce, 0x99, 0xed, 0xf6, 0x0f, 0x21, + 0x9b, 0xd0, 0xf8, 0x15, 0x6c, 0x89, 0x39, 0x2f, 0x9b, 0xc2, 0xc2, 0x47, 0x9c, 0x87, 0x24, 0x2b, + 0x69, 0x51, 0x25, 0x79, 0x56, 0xd6, 0x56, 0xee, 0x80, 0x81, 0x7e, 0x50, 0x11, 0x92, 0xef, 0xdc, + 0xcd, 0x70, 0x4a, 0x4b, 0xae, 0x8b, 0x6c, 0x17, 0xa0, 0x0a, 0x3f, 0x6a, 0x95, 0xa5, 0x7a, 0x78, + 0xd5, 0x6b, 0x2e, 0x5e, 0x6b, 0xa3, 0x78, 0xbb, 0xa0, 0xb0, 0xf6, 0x6b, 0x13, 0xd6, 0x95, 0x6b, + 0x66, 0x5f, 0x79, 0x38, 0x08, 0xa2, 0xff, 0x3f, 0x12, 0xc0, 0xbd, 0x4c, 0xfb, 0xa0, 0x54, 0x37, + 0x4b, 0x5a, 0xcf, 0xc1, 0xeb, 0xc7, 0xb5, 0xfa, 0x53, 0x84, 0xd9, 0xe0, 0xde, 0xbd, 0xc9, 0xc0, + 0xd5, 0xb8, 0xab, 0x6f, 0xd8, 0xc7, 0xa0, 0x70, 0x12, 0x6e, 0x85, 0x7f, 0x36, 0x39, 0x1a, 0x86, + 0xa8, 0x28, 0xfe, 0x3e, 0x9d, 0x86, 0x9e, 0x3f, 0x42, 0x41, 0x75, 0x50, 0x8e, 0x82, 0x60, 0x8c, + 0x4b, 0xd4, 0x06, 0x79, 0xe2, 0x9c, 0xe0, 0x02, 0x31, 0xed, 0xc3, 0xd0, 0xf9, 0x86, 0xfb, 0x83, + 0xa8, 0x7f, 0x36, 0x1e, 0x6f, 0xab, 0xf6, 0x27, 0x50, 0x78, 0x07, 0xa8, 0xd3, 0x7c, 0x95, 0xa6, + 0x5e, 0xe8, 0xd5, 0x8a, 0x60, 0xb9, 0x82, 0x2e, 0xf3, 0xba, 0x38, 0x8a, 0x95, 0x27, 0x71, 0xbd, + 0xfa, 0x08, 0x2d, 0xa3, 0xea, 0x52, 0xec, 0xbd, 0xfd, 0x15, 0xb4, 0x80, 0x1b, 0xc4, 0xe2, 0x4c, + 0xe9, 0x3a, 0x41, 0x0f, 0x94, 0x24, 0x9b, 0xe7, 0xeb, 0x8a, 0x0a, 0xae, 0x87, 0xc8, 0x3d, 0xd5, + 0xe4, 0xa7, 0x54, 0xdb, 0x05, 0xb8, 0x47, 0x6e, 0x8a, 0xf2, 0xe4, 0x47, 0xdd, 0xef, 0xa6, 0xe0, + 0xf2, 0x3f, 0xbf, 0x0b, 0x8d, 0x7f, 0x3e, 0xfc, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x46, 0xe4, 0x99, + 0xf9, 0x13, 0x05, 0x00, 0x00, +} From 2535dc7faf7305c0311df82121d9126100fa5f1d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 05:07:30 -0700 Subject: [PATCH 13/79] index works --- pkg/project/repo.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/project/repo.go b/pkg/project/repo.go index a69cf40..e3fb22b 100644 --- a/pkg/project/repo.go +++ b/pkg/project/repo.go @@ -38,7 +38,12 @@ func (p *Project) AddObjectToIndex(obj *pb.Object) error { return fmt.Errorf("could not retreive index: %v", err) } - return index.Add(entry) + err = index.Add(entry) + if err != nil { + return err + } + + return index.Write() } var ( From 3154a19cdd10d8a50496d1aa720d384a03ad82f7 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 05:28:15 -0700 Subject: [PATCH 14/79] added directory based detection of being in Spread repo --- cli/cli.go | 53 +++++++++++++++++++++++++++++++++++++++++++- cmd/spread/spread.go | 3 ++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index f43d843..0caea68 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -1,10 +1,14 @@ package cli import ( + "errors" "fmt" "io" "os" + "path/filepath" "strings" + + "rsprd.com/spread/pkg/project" ) // SpreadCli is the spread command line client. @@ -19,19 +23,34 @@ type SpreadCli struct { err io.Writer version string + workDir string } // NewSpreadCli returns a spread command line interface (CLI) client.NewSpreadCli. // All functionality accessible from the command line is attached to this struct. -func NewSpreadCli(in io.ReadCloser, out, err io.Writer, version string) *SpreadCli { +func NewSpreadCli(in io.ReadCloser, out, err io.Writer, version, workDir string) *SpreadCli { return &SpreadCli{ in: in, out: out, err: err, version: version, + workDir: workDir, } } +func (c SpreadCli) project() (*project.Project, error) { + if len(c.workDir) == 0 { + return nil, ErrNoWorkDir + } + + root, found := findPath(c.workDir, project.SpreadDirectory, true) + if !found { + return nil, fmt.Errorf("could not find spread directory from %s", c.workDir) + } + + return project.OpenProject(root) +} + func (c SpreadCli) printf(message string, data ...interface{}) { // add newline if doesn't have one if !strings.HasSuffix(message, "\n") { @@ -44,3 +63,35 @@ func (c SpreadCli) fatalf(message string, data ...interface{}) { c.printf(message, data...) os.Exit(1) } + +func findPath(leafDir, targetFile string, dir bool) (string, bool) { + if len(leafDir) == 0 { + return "", false + } + spread := filepath.Join(leafDir, targetFile) + if exists, err := pathExists(spread, dir); err == nil && exists { + return spread, true + } else { + if leafDir == "/" { + return "", false + } + parent := filepath.Dir(leafDir) + return findPath(parent, targetFile, dir) + } +} + +func pathExists(path string, dir bool) (bool, error) { + info, err := os.Stat(path) + if err == nil { + return info.IsDir() == dir, nil + } + if os.IsNotExist(err) { + return false, nil + } + return true, err +} + +var ( + // ErrNoWorkDir is returned when the CLI was started without a working directory set. + ErrNoWorkDir = errors.New("no working directory was set") +) diff --git a/cmd/spread/spread.go b/cmd/spread/spread.go index 77b9277..43217b6 100644 --- a/cmd/spread/spread.go +++ b/cmd/spread/spread.go @@ -10,7 +10,8 @@ import ( ) func main() { - spread := cli.NewSpreadCli(os.Stdin, os.Stdout, os.Stderr, Version) + wd, _ := os.Getwd() + spread := cli.NewSpreadCli(os.Stdin, os.Stdout, os.Stderr, Version, wd) app := app() app.Commands = commands(spread) From 59d1f8733cda29f3988d1a8c51294eac95b53bb2 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 08:31:11 -0700 Subject: [PATCH 15/79] added add command --- cli/add.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 cli/add.go diff --git a/cli/add.go b/cli/add.go new file mode 100644 index 0000000..c778ed3 --- /dev/null +++ b/cli/add.go @@ -0,0 +1,99 @@ +package cli + +import ( + "strings" + + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/data" + "rsprd.com/spread/pkg/deploy" +) + +// Init sets up a Spread repository for versioning. +func (s SpreadCli) Add() *cli.Command { + return &cli.Command{ + Name: "add", + Usage: "spread add ", + Description: "Stage objects to the index", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "namespace", + Value: "default", + Usage: "namespace to look for objects", + }, + cli.StringFlag{ + Name: "context", + Value: "", + Usage: "kubectl context to use for requests", + }, + cli.BoolFlag{ + Name: "no-export", + Usage: "don't request Kube API server to export objects", + }, + }, + Action: func(c *cli.Context) { + // Download specified object from Kubernetes cluster + // example: spread add rc/mattermost + resource := c.Args().First() + if len(resource) == 0 { + s.fatalf("A resource to be added must be specified") + } + + context := c.String("context") + cluster, err := deploy.NewKubeClusterFromContext(context) + if err != nil { + s.fatalf("Failed to connect to Kubernetes cluster: %v", err) + } + + // parse resource type and name + parts := strings.Split(resource, "/") + if len(parts) != 2 { + s.fatalf("Unrecognized resource format") + } + + kind, name := parts[0], parts[1] + namespace := c.String("namespace") + export := !c.Bool("no-export") + req := cluster.Client.Get().Resource(kind).Namespace(namespace).Name(name) + + if export { + req.Param("export", "true") + } + + runObj, err := req.Do().Get() + if err != nil { + s.fatalf("Failed to retrieve resource '%s (namespace=%s)' from Kube API server: %v", resource, namespace, err) + } + + kubeObj, err := deploy.AsKubeObject(runObj) + if err != nil { + s.fatalf("Unable to change into KubeObject: %v", err) + } + // TODO(DG): Clean this up + gvk := kubeObj.GetObjectKind().GroupVersionKind() + gvk.Version = "v1" + kubeObj.GetObjectKind().SetGroupVersionKind(gvk) + kubeObj.GetObjectMeta().SetNamespace(namespace) + + path, err := deploy.ObjectPath(kubeObj) + if err != nil { + s.fatalf("Failed to determine path to save object: %v", err) + } + + obj, err := data.CreateObject(kubeObj.GetObjectMeta().GetName(), path, kubeObj) + if err != nil { + s.fatalf("failed to encode object: %v", err) + } + + proj, err := s.project() + if err != nil { + s.fatalf("Not in a Spread project.") + } + + err = proj.AddObjectToIndex(obj) + if err != nil { + s.fatalf("Failed to add object to Git index: %v", err) + } + }, + } +} From 7b610321b0da3e051a07c0c62711e8b238d42560 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 08:39:32 -0700 Subject: [PATCH 16/79] fix build; add resource short hands --- cli/add.go | 1 + cli/cli.go | 28 ++++++++++++++++++++++++++++ pkg/deploy/cluster.go | 40 ++++++++++++++++++++++++++-------------- pkg/input/dir/source.go | 2 +- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/cli/add.go b/cli/add.go index c778ed3..5c977c2 100644 --- a/cli/add.go +++ b/cli/add.go @@ -52,6 +52,7 @@ func (s SpreadCli) Add() *cli.Command { } kind, name := parts[0], parts[1] + kind = kubeShortForm(kind) namespace := c.String("namespace") export := !c.Bool("no-export") req := cluster.Client.Get().Resource(kind).Namespace(namespace).Name(name) diff --git a/cli/cli.go b/cli/cli.go index 0caea68..9579e1f 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -91,6 +91,34 @@ func pathExists(path string, dir bool) (bool, error) { return true, err } +var shortForms = map[string]string{ + + "cs": "componentstatuses", + "ds": "daemonsets", + "ep": "endpoints", + "ev": "events", + "hpa": "horizontalpodautoscalers", + "ing": "ingresses", + "limits": "limitranges", + "no": "nodes", + "ns": "namespaces", + "po": "pods", + "psp": "podSecurityPolicies", + "pvc": "persistentvolumeclaims", + "pv": "persistentvolumes", + "quota": "resourcequotas", + "rc": "replicationcontrollers", + "rs": "replicasets", + "svc": "services", +} + +func kubeShortForm(resource string) string { + if long, ok := shortForms[resource]; ok { + return long + } + return resource +} + var ( // ErrNoWorkDir is returned when the CLI was started without a working directory set. ErrNoWorkDir = errors.New("no working directory was set") diff --git a/pkg/deploy/cluster.go b/pkg/deploy/cluster.go index 5cdebe3..47049b1 100644 --- a/pkg/deploy/cluster.go +++ b/pkg/deploy/cluster.go @@ -9,6 +9,7 @@ import ( kube "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" rest "k8s.io/kubernetes/pkg/client/restclient" kubecli "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" @@ -23,7 +24,7 @@ const DefaultContext = "" // KubeCluster is able to deploy to Kubernetes clusters. This is a very simple implementation with no error recovery. type KubeCluster struct { - client *kubecli.Client + Client *kubecli.Client context string localkube bool } @@ -62,7 +63,7 @@ func NewKubeClusterFromContext(name string) (*KubeCluster, error) { } return &KubeCluster{ - client: client, + Client: client, context: name, localkube: name == "localkube", }, nil @@ -77,14 +78,14 @@ func (c *KubeCluster) Context() string { // Currently no error recovery is implemented; if there is an error the deployment process will immediately halt and return the error. // If update is not set, will error if objects exist. If deleteModifiedPods is set, pods of modified RCs will be deleted. func (c *KubeCluster) Deploy(dep *Deployment, update, deleteModifiedPods bool) error { - if c.client == nil { + if c.Client == nil { return errors.New("client not setup (was nil)") } // create namespaces before everything else for _, nsObj := range dep.ObjectsOfVersionKind("", "Namespace") { ns := nsObj.(*kube.Namespace) - _, err := c.client.Namespaces().Create(ns) + _, err := c.Client.Namespaces().Create(ns) if err != nil && !alreadyExists(err) { return err } @@ -110,7 +111,7 @@ func (c *KubeCluster) Deploy(dep *Deployment, update, deleteModifiedPods bool) e } } - printLoadBalancers(c.client, dep.ObjectsOfVersionKind("", "Service"), c.localkube) + printLoadBalancers(c.Client, dep.ObjectsOfVersionKind("", "Service"), c.localkube) // deployed successfully return nil @@ -167,7 +168,7 @@ func (c *KubeCluster) update(obj KubeObject, create bool, mapping *meta.RESTMapp return nil, fmt.Errorf("could not create diff: %v", err) } - req := c.client.RESTClient.Patch(kube.StrategicMergePatchType). + req := c.Client.RESTClient.Patch(kube.StrategicMergePatchType). Name(meta.GetName()). Body(patch) @@ -178,12 +179,23 @@ func (c *KubeCluster) update(obj KubeObject, create bool, mapping *meta.RESTMapp return nil, resourceError("update", meta.GetNamespace(), meta.GetName(), mapping, err) } - return asKubeObject(runtimeObj) + return AsKubeObject(runtimeObj) +} + +// Get retrieves an objects from a cluster using it's namespace name and API version. +func (c *KubeCluster) Get(kind, namespace, name, apiVersion string, export bool) (KubeObject, error) { + gv := unversioned.GroupKind{Kind: kind} + mapping, err := kube.RESTMapper.RESTMapping(gv) + if err != nil { + return nil, fmt.Errorf("could not create RESTMapping for %s: %v", gv, err) + } + + return c.get(namespace, name, export, mapping) } // get retrieves the object from the cluster. func (c *KubeCluster) get(namespace, name string, export bool, mapping *meta.RESTMapping) (KubeObject, error) { - req := c.client.RESTClient.Get().Name(name) + req := c.Client.RESTClient.Get().Name(name) setRequestObjectInfo(req, namespace, mapping) if export { @@ -195,13 +207,13 @@ func (c *KubeCluster) get(namespace, name string, export bool, mapping *meta.RES return nil, resourceError("get", namespace, name, mapping, err) } - return asKubeObject(runtimeObj) + return AsKubeObject(runtimeObj) } // create adds the object to the cluster. func (c *KubeCluster) create(obj KubeObject, mapping *meta.RESTMapping) (KubeObject, error) { meta := obj.GetObjectMeta() - req := c.client.RESTClient.Post().Body(obj) + req := c.Client.RESTClient.Post().Body(obj) setRequestObjectInfo(req, meta.GetNamespace(), mapping) @@ -210,7 +222,7 @@ func (c *KubeCluster) create(obj KubeObject, mapping *meta.RESTMapping) (KubeObj return nil, resourceError("create", meta.GetName(), meta.GetNamespace(), mapping, err) } - return asKubeObject(runtimeObj) + return AsKubeObject(runtimeObj) } func (c *KubeCluster) deletePods(rc *kube.ReplicationController) error { @@ -222,14 +234,14 @@ func (c *KubeCluster) deletePods(rc *kube.ReplicationController) error { opts := kube.ListOptions{ LabelSelector: labels.Set(rc.Spec.Selector).AsSelector(), } - podList, err := c.client.Pods(rc.Namespace).List(opts) + podList, err := c.Client.Pods(rc.Namespace).List(opts) if err != nil { return err } // delete pods for _, pod := range podList.Items { - err := c.client.Pods(pod.Namespace).Delete(pod.Name, nil) + err := c.Client.Pods(pod.Namespace).Delete(pod.Name, nil) if err != nil { return err } @@ -306,7 +318,7 @@ func diff(original, modified runtime.Object) (patch []byte, err error) { } // asKubeObject attempts use the object as a KubeObject. It will return an error if not possible. -func asKubeObject(runtimeObj runtime.Object) (KubeObject, error) { +func AsKubeObject(runtimeObj runtime.Object) (KubeObject, error) { kubeObj, ok := runtimeObj.(KubeObject) if !ok { return nil, errors.New("was unable to use runtime.Object as deploy.KubeObject") diff --git a/pkg/input/dir/source.go b/pkg/input/dir/source.go index 0f2a416..b36217e 100644 --- a/pkg/input/dir/source.go +++ b/pkg/input/dir/source.go @@ -76,7 +76,7 @@ func (fs FileSource) Objects() (objects []deploy.KubeObject, err error) { }) // don't throw error if simply didn't find anything - if err != nil && !checkErrNoResources(err) && !checkErrPathDoesNotExist(err) { + if err != nil && !checkErrNoResources(err) && !checkErrPathDoesNotExist(err) && !strings.HasSuffix(err.Error(), "not a directory") { return nil, err } return objects, nil From 41d037f0e4da2a86886564f859f38217fdc5ac1f Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 16 Jun 2016 15:13:26 -0700 Subject: [PATCH 17/79] cleanup after project unit tests --- pkg/project/project_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index e077c0b..3dc19dd 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -26,7 +26,7 @@ func TestInitProjectNoPath(t *testing.T) { func TestInitProjectRelative(t *testing.T) { target := filepath.Join(testDir, "relativeTest") - //defer os.RemoveAll(target) + defer os.RemoveAll(target) proj, err := InitProject(target) assert.NoError(t, err) @@ -40,7 +40,7 @@ func TestInitProjectAbsolute(t *testing.T) { target, err := filepath.Abs(target) assert.NoError(t, err) - //defer os.RemoveAll(target) + defer os.RemoveAll(target) proj, err := InitProject(target) assert.NoError(t, err) From 8f4c33650198af74eb53100782471fbc181ec02d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 10:13:47 -0700 Subject: [PATCH 18/79] moved Get from cluster onto KubeCluster struct --- cli/add.go | 14 ++------------ cli/cli.go | 28 ---------------------------- pkg/deploy/cluster.go | 23 +++++++++++++++++------ pkg/deploy/short.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 46 deletions(-) create mode 100644 pkg/deploy/short.go diff --git a/cli/add.go b/cli/add.go index 5c977c2..e806ccc 100644 --- a/cli/add.go +++ b/cli/add.go @@ -52,24 +52,14 @@ func (s SpreadCli) Add() *cli.Command { } kind, name := parts[0], parts[1] - kind = kubeShortForm(kind) namespace := c.String("namespace") export := !c.Bool("no-export") - req := cluster.Client.Get().Resource(kind).Namespace(namespace).Name(name) - if export { - req.Param("export", "true") - } - - runObj, err := req.Do().Get() + kubeObj, err := cluster.Get(kind, namespace, name, export) if err != nil { - s.fatalf("Failed to retrieve resource '%s (namespace=%s)' from Kube API server: %v", resource, namespace, err) + s.fatalf("Could not get object from cluster: %v", err) } - kubeObj, err := deploy.AsKubeObject(runObj) - if err != nil { - s.fatalf("Unable to change into KubeObject: %v", err) - } // TODO(DG): Clean this up gvk := kubeObj.GetObjectKind().GroupVersionKind() gvk.Version = "v1" diff --git a/cli/cli.go b/cli/cli.go index 9579e1f..0caea68 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -91,34 +91,6 @@ func pathExists(path string, dir bool) (bool, error) { return true, err } -var shortForms = map[string]string{ - - "cs": "componentstatuses", - "ds": "daemonsets", - "ep": "endpoints", - "ev": "events", - "hpa": "horizontalpodautoscalers", - "ing": "ingresses", - "limits": "limitranges", - "no": "nodes", - "ns": "namespaces", - "po": "pods", - "psp": "podSecurityPolicies", - "pvc": "persistentvolumeclaims", - "pv": "persistentvolumes", - "quota": "resourcequotas", - "rc": "replicationcontrollers", - "rs": "replicasets", - "svc": "services", -} - -func kubeShortForm(resource string) string { - if long, ok := shortForms[resource]; ok { - return long - } - return resource -} - var ( // ErrNoWorkDir is returned when the CLI was started without a working directory set. ErrNoWorkDir = errors.New("no working directory was set") diff --git a/pkg/deploy/cluster.go b/pkg/deploy/cluster.go index 47049b1..11cc736 100644 --- a/pkg/deploy/cluster.go +++ b/pkg/deploy/cluster.go @@ -9,7 +9,6 @@ import ( kube "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/api/unversioned" rest "k8s.io/kubernetes/pkg/client/restclient" kubecli "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" @@ -183,14 +182,26 @@ func (c *KubeCluster) update(obj KubeObject, create bool, mapping *meta.RESTMapp } // Get retrieves an objects from a cluster using it's namespace name and API version. -func (c *KubeCluster) Get(kind, namespace, name, apiVersion string, export bool) (KubeObject, error) { - gv := unversioned.GroupKind{Kind: kind} - mapping, err := kube.RESTMapper.RESTMapping(gv) +func (c *KubeCluster) Get(kind, namespace, name string, export bool) (KubeObject, error) { + kind = KubeShortForm(kind) + + req := c.Client.Get().Resource(kind).Namespace(namespace).Name(name) + + if export { + req.Param("export", "true") + } + + runObj, err := req.Do().Get() if err != nil { - return nil, fmt.Errorf("could not create RESTMapping for %s: %v", gv, err) + return nil, fmt.Errorf("Failed to retrieve resource '%s/%s (namespace=%s)' from Kube API server: %v", kind, name, namespace, err) } - return c.get(namespace, name, export, mapping) + kubeObj, err := AsKubeObject(runObj) + if err != nil { + return nil, fmt.Errorf("Unable to change into KubeObject: %v", err) + } + + return kubeObj, nil } // get retrieves the object from the cluster. diff --git a/pkg/deploy/short.go b/pkg/deploy/short.go new file mode 100644 index 0000000..41d8efd --- /dev/null +++ b/pkg/deploy/short.go @@ -0,0 +1,28 @@ +package deploy + +var shortForms = map[string]string{ + "cs": "componentstatuses", + "ds": "daemonsets", + "ep": "endpoints", + "ev": "events", + "hpa": "horizontalpodautoscalers", + "ing": "ingresses", + "limits": "limitranges", + "no": "nodes", + "ns": "namespaces", + "po": "pods", + "psp": "podSecurityPolicies", + "pvc": "persistentvolumeclaims", + "pv": "persistentvolumes", + "quota": "resourcequotas", + "rc": "replicationcontrollers", + "rs": "replicasets", + "svc": "services", +} + +func KubeShortForm(resource string) string { + if long, ok := shortForms[resource]; ok { + return long + } + return resource +} From 00c5bd873999d1907033e70e63aabd116ef63eb6 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 11:08:51 -0700 Subject: [PATCH 19/79] added commit --- cli/add.go | 8 ++---- cli/cli.go | 12 +++++--- cli/commit.go | 46 +++++++++++++++++++++++++++++ pkg/project/{repo.go => add.go} | 0 pkg/project/author.go | 11 +++++++ pkg/project/commit.go | 51 +++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 cli/commit.go rename pkg/project/{repo.go => add.go} (100%) create mode 100644 pkg/project/author.go create mode 100644 pkg/project/commit.go diff --git a/cli/add.go b/cli/add.go index e806ccc..ce5ccac 100644 --- a/cli/add.go +++ b/cli/add.go @@ -9,7 +9,7 @@ import ( "rsprd.com/spread/pkg/deploy" ) -// Init sets up a Spread repository for versioning. +// Add sets up a Spread repository for versioning. func (s SpreadCli) Add() *cli.Command { return &cli.Command{ Name: "add", @@ -76,11 +76,7 @@ func (s SpreadCli) Add() *cli.Command { s.fatalf("failed to encode object: %v", err) } - proj, err := s.project() - if err != nil { - s.fatalf("Not in a Spread project.") - } - + proj := s.project() err = proj.AddObjectToIndex(obj) if err != nil { s.fatalf("Failed to add object to Git index: %v", err) diff --git a/cli/cli.go b/cli/cli.go index 0caea68..f5daadc 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -38,17 +38,21 @@ func NewSpreadCli(in io.ReadCloser, out, err io.Writer, version, workDir string) } } -func (c SpreadCli) project() (*project.Project, error) { +func (c SpreadCli) project() *project.Project { if len(c.workDir) == 0 { - return nil, ErrNoWorkDir + c.fatalf("Encountered error: %v", ErrNoWorkDir) } root, found := findPath(c.workDir, project.SpreadDirectory, true) if !found { - return nil, fmt.Errorf("could not find spread directory from %s", c.workDir) + c.fatalf("Not in a Spread project.") } - return project.OpenProject(root) + proj, err := project.OpenProject(root) + if err != nil { + c.fatalf("Error opening project: %v", err) + } + return proj } func (c SpreadCli) printf(message string, data ...interface{}) { diff --git a/cli/commit.go b/cli/commit.go new file mode 100644 index 0000000..fca151b --- /dev/null +++ b/cli/commit.go @@ -0,0 +1,46 @@ +package cli + +import ( + "time" + + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/project" +) + +// Commit sets up a Spread repository for versioning. +func (s SpreadCli) Commit() *cli.Command { + return &cli.Command{ + Name: "commit", + Usage: "spread commit -m ", + Description: "Create new commit based on the current index", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "m", + Usage: "Message to store the commit with", + }, + }, + Action: func(c *cli.Context) { + // Download specified object from Kubernetes cluster + // example: spread add rc/mattermost + msg := c.String("m") + if len(msg) == 0 { + s.fatalf("All commits must have a message. Specify one with 'spread commit -m \"message\"'") + } + + proj := s.project() + notImplemented := project.Person{ + Name: "not implemented", + Email: "not@implemented.com", + When: time.Now(), + } + + oid, err := proj.Commit("HEAD", notImplemented, notImplemented, msg) + if err != nil { + s.fatalf("Could not commit: %v", err) + } + + s.printf("New commit: [%s] %s", oid, msg) + }, + } +} diff --git a/pkg/project/repo.go b/pkg/project/add.go similarity index 100% rename from pkg/project/repo.go rename to pkg/project/add.go diff --git a/pkg/project/author.go b/pkg/project/author.go new file mode 100644 index 0000000..3307bfe --- /dev/null +++ b/pkg/project/author.go @@ -0,0 +1,11 @@ +package project + +import ( + "time" +) + +type Person struct { + Name string + Email string + When time.Time +} diff --git a/pkg/project/commit.go b/pkg/project/commit.go new file mode 100644 index 0000000..fa24358 --- /dev/null +++ b/pkg/project/commit.go @@ -0,0 +1,51 @@ +package project + +import ( + "fmt" + + git "gopkg.in/libgit2/git2go.v23" +) + +func (p *Project) Commit(refname string, author, committer Person, message string) (commitOid string, err error) { + var parents []*git.Commit + if head := p.headCommit(); head != nil { + parents = append(parents, head) + } + + gitAuthor, gitCommitter := git.Signature(author), git.Signature(committer) + + index, err := p.repo.Index() + if err != nil { + return "", fmt.Errorf("could not get index: %v", err) + } + + treeOid, err := index.WriteTree() + if err != nil { + return "", fmt.Errorf("could not write index to tree: %v", err) + } + + commitTree, err := p.repo.LookupTree(treeOid) + if err != nil { + return "", fmt.Errorf("could not retrieve created commit tree: %v", err) + } + + commit, err := p.repo.CreateCommit(refname, &gitAuthor, &gitCommitter, message, commitTree, parents...) + if err != nil { + return "", fmt.Errorf("failed to create commit: %v", err) + } + + return commit.String(), nil +} + +func (p *Project) headCommit() *git.Commit { + ref, err := p.repo.Head() + if err != nil { + return nil + } + + commit, err := ref.Peel(git.ObjectCommit) + if err != nil { + return nil + } + return commit.(*git.Commit) +} From e03902f696164322c018c36a83021173daae0ed1 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 13:29:31 -0700 Subject: [PATCH 20/79] wrote protobuf Object decoding, and started object repository retrieval --- cli/commit.go | 2 - pkg/data/decode.go | 66 +++++++++++++++++++++++++++++++ pkg/data/{fields.go => encode.go} | 0 pkg/data/object.go | 45 +++++++++++++++++++-- pkg/deploy/kinds.go | 64 ++++++++++++++++++++++++++++++ pkg/deploy/object.go | 20 +++++++++- pkg/deploy/short.go | 28 ------------- pkg/project/{add.go => index.go} | 0 pkg/project/object.go | 24 +++++++++++ 9 files changed, 214 insertions(+), 35 deletions(-) create mode 100644 pkg/data/decode.go rename pkg/data/{fields.go => encode.go} (100%) create mode 100644 pkg/deploy/kinds.go delete mode 100644 pkg/deploy/short.go rename pkg/project/{add.go => index.go} (100%) create mode 100644 pkg/project/object.go diff --git a/cli/commit.go b/cli/commit.go index fca151b..a44a1bf 100644 --- a/cli/commit.go +++ b/cli/commit.go @@ -21,8 +21,6 @@ func (s SpreadCli) Commit() *cli.Command { }, }, Action: func(c *cli.Context) { - // Download specified object from Kubernetes cluster - // example: spread add rc/mattermost msg := c.String("m") if len(msg) == 0 { s.fatalf("All commits must have a message. Specify one with 'spread commit -m \"message\"'") diff --git a/pkg/data/decode.go b/pkg/data/decode.go new file mode 100644 index 0000000..9911b68 --- /dev/null +++ b/pkg/data/decode.go @@ -0,0 +1,66 @@ +package data + +import ( + "fmt" + "strconv" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +func decodeField(field *pb.Field) (interface{}, error) { + val := field.GetValue() + if val == nil { + return nil, fmt.Errorf("value for '%s' was nil", field.Key) + } + + switch val.Type { + case pb.FieldValue_NUMBER: + return strconv.ParseFloat(val.Value, 64) + case pb.FieldValue_STRING: + return val.Value, nil + case pb.FieldValue_BOOL: + return strconv.ParseBool(val.Value) + case pb.FieldValue_NULL: + return nil, nil + case pb.FieldValue_MAP: + return decodeMapField(field) + case pb.FieldValue_ARRAY: + return decodeArrayField(field) + } + + return nil, fmt.Errorf("unknown type for Field '%s'", field.Key) +} + +func decodeMapField(root *pb.Field) (map[string]interface{}, error) { + fields := root.GetFields() + if fields == nil { + return nil, fmt.Errorf("field '%s' is a map with nil for Fields", root.Key) + } + + out := make(map[string]interface{}, len(fields)) + for _, field := range fields { + val, err := decodeField(field) + if err != nil { + return nil, fmt.Errorf("couldn't decode '%s': %v", field.Key, err) + } + out[field.Key] = val + } + return out, nil +} + +func decodeArrayField(root *pb.Field) ([]interface{}, error) { + fields := root.GetFields() + if fields == nil { + return nil, fmt.Errorf("field '%s' is a array with nil for Fields", root.Key) + } + + out := make([]interface{}, len(fields)) + for i, field := range fields { + val, err := decodeField(field) + if err != nil { + return nil, fmt.Errorf("couldn't decode '%s': %v", field.Key, err) + } + out[i] = val + } + return out, nil +} diff --git a/pkg/data/fields.go b/pkg/data/encode.go similarity index 100% rename from pkg/data/fields.go rename to pkg/data/encode.go diff --git a/pkg/data/object.go b/pkg/data/object.go index ce70861..1ce2bb7 100644 --- a/pkg/data/object.go +++ b/pkg/data/object.go @@ -2,6 +2,7 @@ package data import ( "encoding/json" + "errors" "fmt" pb "rsprd.com/spread/pkg/spreadproto" @@ -14,19 +15,19 @@ func CreateObject(name, path string, ptr interface{}) (*pb.Object, error) { return nil, fmt.Errorf("unable to generate JSON object: %v", err) } - // this is a bit hacky but not sure of a better way to ensure proper tagging + // TODO: use reflection to replace this var jsonData map[string]interface{} err = json.Unmarshal(data, &jsonData) if err != nil { return nil, err } - return CreateObjectFromMap(name, path, jsonData) + return ObjectFromMap(name, path, jsonData) } -// CreateObjectFromMap creates an Object, using the entries of a map as fields. +// ObjectFromMap creates an Object, using the entries of a map as fields. // This supports maps embedded as values. It is assumed that types are limited to JSON types. -func CreateObjectFromMap(name, path string, data map[string]interface{}) (*pb.Object, error) { +func ObjectFromMap(name, path string, data map[string]interface{}) (*pb.Object, error) { obj := &pb.Object{ Name: name, Info: &pb.ObjectInfo{ @@ -46,3 +47,39 @@ func CreateObjectFromMap(name, path string, data map[string]interface{}) (*pb.Ob } return obj, nil } + +func Unmarshal(obj *pb.Object, ptr interface{}) error { + fieldMap, err := MapFromObject(obj) + if err != nil { + return err + } + + // TODO: use reflection to replace this + jsonData, err := json.Marshal(&fieldMap) + if err != nil { + return fmt.Errorf("unable to generate JSON from object data: %v", err) + } + + return json.Unmarshal(jsonData, ptr) +} + +func MapFromObject(obj *pb.Object) (map[string]interface{}, error) { + fields := obj.GetFields() + if fields == nil { + return nil, ErrObjectNilFields + } + + out := make(map[string]interface{}, len(fields)) + for _, field := range fields { + val, err := decodeField(field) + if err != nil { + return nil, fmt.Errorf("could not decode field '%s': %v", field.Key, err) + } + out[field.Key] = val + } + return out, nil +} + +var ( + ErrObjectNilFields = errors.New("object had nil for Fields") +) diff --git a/pkg/deploy/kinds.go b/pkg/deploy/kinds.go new file mode 100644 index 0000000..54584c4 --- /dev/null +++ b/pkg/deploy/kinds.go @@ -0,0 +1,64 @@ +package deploy + +import ( + kube "k8s.io/kubernetes/pkg/api" +) + +var shortForms = map[string]string{ + "cs": "componentstatuses", + "ds": "daemonsets", + "ep": "endpoints", + "ev": "events", + "hpa": "horizontalpodautoscalers", + "ing": "ingresses", + "limits": "limitranges", + "no": "nodes", + "ns": "namespaces", + "po": "pods", + "psp": "podSecurityPolicies", + "pvc": "persistentvolumeclaims", + "pv": "persistentvolumes", + "quota": "resourcequotas", + "rc": "replicationcontrollers", + "rs": "replicasets", + "svc": "services", +} + +func KubeShortForm(resource string) string { + if long, ok := shortForms[resource]; ok { + return long + } + return resource +} + +// TODO: ensure all Kinds are supported +var kinds = map[string]KubeObject{ + "componentstatus": &kube.ComponentStatus{}, + "endpoint": &kube.Endpoints{}, + "event": &kube.Event{}, + "limitrange": &kube.LimitRange{}, + "node": &kube.Node{}, + "namespace": &kube.Namespace{}, + "pod": &kube.Pod{}, + "persistentvolumeclaim": &kube.PersistentVolumeClaim{}, + "persistentvolume": &kube.PersistentVolume{}, + "resourcequota": &kube.ResourceQuota{}, + "replicationcontroller": &kube.ReplicationController{}, + "service": &kube.Service{}, + "secret": &kube.Secret{}, +} + +// BaseObject returns a Kubernetes object of the given kind to be used to populate. +// Nil is returned if the Kind is unknown +func BaseObject(kind string) KubeObject { + obj, ok := kinds[kind] + if !ok { + return nil + } + + copy, err := kube.Scheme.DeepCopy(obj) + if err != nil { + return nil + } + return copy.(KubeObject) +} diff --git a/pkg/deploy/object.go b/pkg/deploy/object.go index 0b6462c..dca10fb 100644 --- a/pkg/deploy/object.go +++ b/pkg/deploy/object.go @@ -3,11 +3,15 @@ package deploy import ( "errors" "fmt" + "strings" kube "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" types "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" + + "rsprd.com/spread/pkg/data" + pb "rsprd.com/spread/pkg/spreadproto" ) // A KubeObject is an alias for Kubernetes objects. @@ -26,7 +30,8 @@ func ObjectPath(obj KubeObject) (string, error) { } meta := obj.GetObjectMeta() - return fmt.Sprintf("%s/namespaces/%s/%s/%s", obj.GetObjectKind().GroupVersionKind().Version, meta.GetNamespace(), gkv.Kind, meta.GetName()), nil + path := fmt.Sprintf("%s/namespaces/%s/%s/%s", obj.GetObjectKind().GroupVersionKind().Version, meta.GetNamespace(), gkv.Kind, meta.GetName()) + return strings.ToLower(path), nil } // objectKind is a helper function which determines type information from given KubeObject. @@ -40,3 +45,16 @@ func objectKind(obj KubeObject) (types.GroupVersionKind, error) { } return gkv, nil } + +func KubeObjectFromObject(kind string, obj *pb.Object) (KubeObject, error) { + base := BaseObject(kind) + if base == nil { + return nil, fmt.Errorf("unable to find Kind for '%s'", kind) + } + + err := data.Unmarshal(obj, &base) + if err != nil { + return nil, err + } + return base, nil +} diff --git a/pkg/deploy/short.go b/pkg/deploy/short.go deleted file mode 100644 index 41d8efd..0000000 --- a/pkg/deploy/short.go +++ /dev/null @@ -1,28 +0,0 @@ -package deploy - -var shortForms = map[string]string{ - "cs": "componentstatuses", - "ds": "daemonsets", - "ep": "endpoints", - "ev": "events", - "hpa": "horizontalpodautoscalers", - "ing": "ingresses", - "limits": "limitranges", - "no": "nodes", - "ns": "namespaces", - "po": "pods", - "psp": "podSecurityPolicies", - "pvc": "persistentvolumeclaims", - "pv": "persistentvolumes", - "quota": "resourcequotas", - "rc": "replicationcontrollers", - "rs": "replicasets", - "svc": "services", -} - -func KubeShortForm(resource string) string { - if long, ok := shortForms[resource]; ok { - return long - } - return resource -} diff --git a/pkg/project/add.go b/pkg/project/index.go similarity index 100% rename from pkg/project/add.go rename to pkg/project/index.go diff --git a/pkg/project/object.go b/pkg/project/object.go new file mode 100644 index 0000000..b47cb0a --- /dev/null +++ b/pkg/project/object.go @@ -0,0 +1,24 @@ +package project + +import ( + "fmt" + + "github.com/golang/protobuf/proto" + git "gopkg.in/libgit2/git2go.v23" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +func (p *Project) getObject(oid *git.Oid) (*pb.Object, error) { + blob, err := p.repo.LookupBlob(oid) + if err != nil { + return nil, fmt.Errorf("failed to retrieve Object blob: %v", err) + } + + var obj *pb.Object + err = proto.Unmarshal(blob.Contents(), obj) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal object protobuf: %v", err) + } + return obj, nil +} From c4cf851388109a051bd865b01550fc2b91864912 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 14:02:00 -0700 Subject: [PATCH 21/79] create deployment from Git index --- pkg/data/decode.go | 4 ++-- pkg/project/index.go | 47 +++++++++++++++++++++++++++++++++++++++++++ pkg/project/object.go | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/pkg/data/decode.go b/pkg/data/decode.go index 9911b68..fe3dd26 100644 --- a/pkg/data/decode.go +++ b/pkg/data/decode.go @@ -34,7 +34,7 @@ func decodeField(field *pb.Field) (interface{}, error) { func decodeMapField(root *pb.Field) (map[string]interface{}, error) { fields := root.GetFields() if fields == nil { - return nil, fmt.Errorf("field '%s' is a map with nil for Fields", root.Key) + return nil, nil } out := make(map[string]interface{}, len(fields)) @@ -51,7 +51,7 @@ func decodeMapField(root *pb.Field) (map[string]interface{}, error) { func decodeArrayField(root *pb.Field) ([]interface{}, error) { fields := root.GetFields() if fields == nil { - return nil, fmt.Errorf("field '%s' is a array with nil for Fields", root.Key) + return nil, nil } out := make([]interface{}, len(fields)) diff --git a/pkg/project/index.go b/pkg/project/index.go index e3fb22b..e1a8630 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -3,10 +3,12 @@ package project import ( "errors" "fmt" + "strings" "github.com/golang/protobuf/proto" git "gopkg.in/libgit2/git2go.v23" + "rsprd.com/spread/pkg/deploy" pb "rsprd.com/spread/pkg/spreadproto" ) @@ -46,6 +48,51 @@ func (p *Project) AddObjectToIndex(obj *pb.Object) error { return index.Write() } +func (p *Project) Index() (*deploy.Deployment, error) { + index, err := p.repo.Index() + if err != nil { + return nil, fmt.Errorf("could not retrieve index: %v", err) + } + + deployment := new(deploy.Deployment) + indexSize := int(index.EntryCount()) + for i := 0; i < indexSize; i++ { + entry, err := index.EntryByIndex(uint(i)) + if err != nil { + return nil, fmt.Errorf("failed to retrieve index entry: %v", err) + } + + obj, err := p.getObject(entry.Id) + if err != nil { + return nil, fmt.Errorf("failed to read object from Git repository: %v", err) + } + + kind, err := kindFromPath(entry.Path) + if err != nil { + return nil, err + } + + kubeObj, err := deploy.KubeObjectFromObject(kind, obj) + if err != nil { + return nil, err + } + + err = deployment.Add(kubeObj) + if err != nil { + return nil, fmt.Errorf("could not add object to deployment: %v", err) + } + } + return deployment, nil +} + +func kindFromPath(path string) (string, error) { + parts := strings.Split(path, "/") + if len(parts) != 5 { + return "", fmt.Errorf("path wrong length (is %d, expected 5)", len(parts)) + } + return parts[3], nil +} + var ( ErrNilObjectInfo = errors.New("an object's Info field cannot be nil") ) diff --git a/pkg/project/object.go b/pkg/project/object.go index b47cb0a..1561442 100644 --- a/pkg/project/object.go +++ b/pkg/project/object.go @@ -15,7 +15,7 @@ func (p *Project) getObject(oid *git.Oid) (*pb.Object, error) { return nil, fmt.Errorf("failed to retrieve Object blob: %v", err) } - var obj *pb.Object + obj := &pb.Object{} err = proto.Unmarshal(blob.Contents(), obj) if err != nil { return nil, fmt.Errorf("unable to unmarshal object protobuf: %v", err) From b4df6eee9d4881b506177c09a37b4561efab0e11 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 16:17:31 -0700 Subject: [PATCH 22/79] added deployment creation from commits --- pkg/project/commit.go | 49 ++++++++++++++++++++++++++++++++++++++----- pkg/project/index.go | 12 +---------- pkg/project/object.go | 19 +++++++++++++++++ pkg/project/tree.go | 40 +++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 pkg/project/tree.go diff --git a/pkg/project/commit.go b/pkg/project/commit.go index fa24358..73a4039 100644 --- a/pkg/project/commit.go +++ b/pkg/project/commit.go @@ -4,11 +4,13 @@ import ( "fmt" git "gopkg.in/libgit2/git2go.v23" + + "rsprd.com/spread/pkg/deploy" ) func (p *Project) Commit(refname string, author, committer Person, message string) (commitOid string, err error) { var parents []*git.Commit - if head := p.headCommit(); head != nil { + if head, err := p.headCommit(); err == nil { parents = append(parents, head) } @@ -37,15 +39,52 @@ func (p *Project) Commit(refname string, author, committer Person, message strin return commit.String(), nil } -func (p *Project) headCommit() *git.Commit { +func (p *Project) Head() (*deploy.Deployment, error) { + commit, err := p.headCommit() + if err != nil { + return nil, fmt.Errorf("could not retrieve head: %v", err) + } + + tree, err := commit.Tree() + if err != nil { + return nil, fmt.Errorf("couldn't get tree for HEAD: %v", err) + } + + return p.deploymentFromTree(tree) +} + +func (p *Project) ResolveCommit(revision string) (*deploy.Deployment, error) { + gitObj, err := p.repo.RevparseSingle(revision) + if err != nil { + return nil, fmt.Errorf("couldn't resolve revspec '%s': %v", revision, err) + } + + if gitObj.Type() != git.ObjectCommit { + return nil, fmt.Errorf("'%s' specifies an object other than a commit", revision) + } + + commit, err := gitObj.Peel(git.ObjectCommit) + if err != nil { + return nil, err + } + + tree, err := commit.(*git.Commit).Tree() + if err != nil { + return nil, err + } + + return p.deploymentFromTree(tree) +} + +func (p *Project) headCommit() (*git.Commit, error) { ref, err := p.repo.Head() if err != nil { - return nil + return nil, err } commit, err := ref.Peel(git.ObjectCommit) if err != nil { - return nil + return nil, err } - return commit.(*git.Commit) + return commit.(*git.Commit), nil } diff --git a/pkg/project/index.go b/pkg/project/index.go index e1a8630..e7f090c 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -62,17 +62,7 @@ func (p *Project) Index() (*deploy.Deployment, error) { return nil, fmt.Errorf("failed to retrieve index entry: %v", err) } - obj, err := p.getObject(entry.Id) - if err != nil { - return nil, fmt.Errorf("failed to read object from Git repository: %v", err) - } - - kind, err := kindFromPath(entry.Path) - if err != nil { - return nil, err - } - - kubeObj, err := deploy.KubeObjectFromObject(kind, obj) + kubeObj, err := p.getKubeObject(entry.Id, entry.Path) if err != nil { return nil, err } diff --git a/pkg/project/object.go b/pkg/project/object.go index 1561442..58d01bc 100644 --- a/pkg/project/object.go +++ b/pkg/project/object.go @@ -6,6 +6,7 @@ import ( "github.com/golang/protobuf/proto" git "gopkg.in/libgit2/git2go.v23" + "rsprd.com/spread/pkg/deploy" pb "rsprd.com/spread/pkg/spreadproto" ) @@ -22,3 +23,21 @@ func (p *Project) getObject(oid *git.Oid) (*pb.Object, error) { } return obj, nil } + +func (p *Project) getKubeObject(oid *git.Oid, path string) (deploy.KubeObject, error) { + obj, err := p.getObject(oid) + if err != nil { + return nil, fmt.Errorf("failed to read object from Git repository: %v", err) + } + + kind, err := kindFromPath(path) + if err != nil { + return nil, err + } + + kubeObj, err := deploy.KubeObjectFromObject(kind, obj) + if err != nil { + return nil, err + } + return kubeObj, nil +} diff --git a/pkg/project/tree.go b/pkg/project/tree.go new file mode 100644 index 0000000..2813710 --- /dev/null +++ b/pkg/project/tree.go @@ -0,0 +1,40 @@ +package project + +import ( + "fmt" + + git "gopkg.in/libgit2/git2go.v23" + + "rsprd.com/spread/pkg/deploy" +) + +func (p *Project) deploymentFromTree(tree *git.Tree) (*deploy.Deployment, error) { + deployment := new(deploy.Deployment) + var walkErr error + err := tree.Walk(func(path string, entry *git.TreeEntry) int { + // add objects to deployment + if entry.Type == git.ObjectBlob { + kubeObj, err := p.getKubeObject(entry.Id, path) + if err != nil { + walkErr = err + return -1 + } + + walkErr = deployment.Add(kubeObj) + if walkErr != nil { + return -1 + } + } + return 0 + }) + + if err != nil { + return nil, fmt.Errorf("error starting walk: %v", err) + } + + if walkErr != nil { + return nil, fmt.Errorf("error during walk: %v", err) + } + + return deployment, nil +} From ccdd02664cca14eb19da966878aae274600625e8 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 16:22:05 -0700 Subject: [PATCH 23/79] fix object tests --- pkg/deploy/object_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/deploy/object_test.go b/pkg/deploy/object_test.go index ea3dbab..aa24e06 100644 --- a/pkg/deploy/object_test.go +++ b/pkg/deploy/object_test.go @@ -19,7 +19,7 @@ func TestObjectPath(t *testing.T) { }, } - expected := "v1/namespaces/default/ReplicationController/johnson" + expected := "v1/namespaces/default/replicationcontroller/johnson" actual, err := ObjectPath(rc) assert.NoError(t, err) assert.Equal(t, expected, actual) @@ -35,7 +35,7 @@ func TestObjectPathNoNamespace(t *testing.T) { }, } - expected := "v1/namespaces//ReplicationController/johnson" + expected := "v1/namespaces//replicationcontroller/johnson" actual, err := ObjectPath(rc) assert.NoError(t, err) assert.Equal(t, expected, actual) From d6181fd73afcb16707d5a5c2d8a4a1c9f2330c0a Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 19:15:36 -0700 Subject: [PATCH 24/79] Added diff and hacky cluster deployment retrieval --- cli/diff.go | 35 +++++++++++++ pkg/deploy/cluster.go | 102 ++++++++++++++++++++++++++++++++++++++ pkg/deploy/kinds.go | 18 +++++++ pkg/deploy/object.go | 2 +- pkg/deploy/object_test.go | 4 +- pkg/project/index.go | 4 +- 6 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 cli/diff.go diff --git a/cli/diff.go b/cli/diff.go new file mode 100644 index 0000000..b79ba36 --- /dev/null +++ b/cli/diff.go @@ -0,0 +1,35 @@ +package cli + +import ( + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/deploy" +) + +// Status returns information about the current state of the project. +func (s SpreadCli) Diff() *cli.Command { + return &cli.Command{ + Name: "diff", + Usage: "spread diff", + Description: "Diffs index against state of cluster", + Action: func(c *cli.Context) { + proj := s.project() + index, err := proj.Index() + if err != nil { + s.fatalf("Could not load Index: %v", err) + } + + client, err := deploy.NewKubeClusterFromContext("") + if err != nil { + s.fatalf("Failed to connect to Kubernetes cluster: %v", err) + } + + cluster, err := client.Deployment() + if err != nil { + s.fatalf("Could not load deployment from cluster: %v", err) + } + + s.printf(index.Diff(cluster)) + }, + } +} diff --git a/pkg/deploy/cluster.go b/pkg/deploy/cluster.go index 11cc736..bad5ee9 100644 --- a/pkg/deploy/cluster.go +++ b/pkg/deploy/cluster.go @@ -260,6 +260,108 @@ func (c *KubeCluster) deletePods(rc *kube.ReplicationController) error { return nil } +func (c *KubeCluster) Deployment() (*Deployment, error) { + deployment := new(Deployment) + for _, resource := range resources { + obj, err := c.Client.Get().Resource(resource).Do().Get() + if err != nil { + return nil, fmt.Errorf("could not list '%s': %v", resource, err) + } + + // TODO: this is what desperation looks like + switch t := obj.(type) { + case *kube.ComponentStatusList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.ConfigMapList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.EndpointsList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.EventList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.LimitRangeList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.NamespaceList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.PersistentVolumeClaimList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.PersistentVolumeList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.PodList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.ReplicationControllerList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.ResourceQuotaList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.SecretList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.ServiceAccountList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + case *kube.ServiceList: + for _, item := range t.Items { + if err := deployment.Add(&item); err != nil { + return nil, err + } + } + default: + return nil, fmt.Errorf("could not match '%T' to type", obj) + } + + } + return deployment, nil +} + // setRequestObjectInfo adds necessary type information to requests. func setRequestObjectInfo(req *rest.Request, namespace string, mapping *meta.RESTMapping) { // if namespace scoped resource, set namespace diff --git a/pkg/deploy/kinds.go b/pkg/deploy/kinds.go index 54584c4..098b84e 100644 --- a/pkg/deploy/kinds.go +++ b/pkg/deploy/kinds.go @@ -33,6 +33,8 @@ func KubeShortForm(resource string) string { // TODO: ensure all Kinds are supported var kinds = map[string]KubeObject{ + "configmap": &kube.ConfigMap{}, + "serviceaccount": &kube.ServiceAccount{}, "componentstatus": &kube.ComponentStatus{}, "endpoint": &kube.Endpoints{}, "event": &kube.Event{}, @@ -48,6 +50,22 @@ var kinds = map[string]KubeObject{ "secret": &kube.Secret{}, } +var resources = []string{ + "configmaps", + "endpoints", + "events", + "limitranges", + "namespaces", + "persistentvolumeclaims", + "persistentvolumes", + "pods", + "replicationcontrollers", + "resourcequotas", + "secrets", + "serviceaccounts", + "services", +} + // BaseObject returns a Kubernetes object of the given kind to be used to populate. // Nil is returned if the Kind is unknown func BaseObject(kind string) KubeObject { diff --git a/pkg/deploy/object.go b/pkg/deploy/object.go index dca10fb..c483c21 100644 --- a/pkg/deploy/object.go +++ b/pkg/deploy/object.go @@ -30,7 +30,7 @@ func ObjectPath(obj KubeObject) (string, error) { } meta := obj.GetObjectMeta() - path := fmt.Sprintf("%s/namespaces/%s/%s/%s", obj.GetObjectKind().GroupVersionKind().Version, meta.GetNamespace(), gkv.Kind, meta.GetName()) + path := fmt.Sprintf("namespaces/%s/%s/%s", meta.GetNamespace(), gkv.Kind, meta.GetName()) return strings.ToLower(path), nil } diff --git a/pkg/deploy/object_test.go b/pkg/deploy/object_test.go index aa24e06..1636d18 100644 --- a/pkg/deploy/object_test.go +++ b/pkg/deploy/object_test.go @@ -19,7 +19,7 @@ func TestObjectPath(t *testing.T) { }, } - expected := "v1/namespaces/default/replicationcontroller/johnson" + expected := "namespaces/default/replicationcontroller/johnson" actual, err := ObjectPath(rc) assert.NoError(t, err) assert.Equal(t, expected, actual) @@ -35,7 +35,7 @@ func TestObjectPathNoNamespace(t *testing.T) { }, } - expected := "v1/namespaces//replicationcontroller/johnson" + expected := "namespaces//replicationcontroller/johnson" actual, err := ObjectPath(rc) assert.NoError(t, err) assert.Equal(t, expected, actual) diff --git a/pkg/project/index.go b/pkg/project/index.go index e7f090c..9127fb1 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -77,10 +77,10 @@ func (p *Project) Index() (*deploy.Deployment, error) { func kindFromPath(path string) (string, error) { parts := strings.Split(path, "/") - if len(parts) != 5 { + if len(parts) != 4 { return "", fmt.Errorf("path wrong length (is %d, expected 5)", len(parts)) } - return parts[3], nil + return parts[2], nil } var ( From f68847974bd4edcd730141769152aa073fb523fa Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 19:36:59 -0700 Subject: [PATCH 25/79] added deploying from commits to deploy --- cli/deploy.go | 81 ++++++++++++++++++++++++++++++--------------------- cli/diff.go | 12 ++++++-- cli/status.go | 35 ++++++++++++++++++++++ 3 files changed, 93 insertions(+), 35 deletions(-) create mode 100644 cli/status.go diff --git a/cli/deploy.go b/cli/deploy.go index c070c5a..8704fe2 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -15,44 +15,25 @@ import ( func (s SpreadCli) Deploy() *cli.Command { return &cli.Command{ Name: "deploy", - Usage: "spread deploy [-s] PATH [kubectl context]", + Usage: "spread deploy [-s] PATH | COMMIT [kubectl context]", Description: "Deploys objects to a remote Kubernetes cluster.", ArgsUsage: "-s will deploy only if no other deployment found (otherwise fails)", Action: func(c *cli.Context) { - srcDir := c.Args().First() - if len(srcDir) == 0 { - s.fatalf("A directory to deploy from must be specified") - } - - input, err := dir.NewFileInput(srcDir) - if err != nil { - s.fatalf(inputError(srcDir, err)) - } - - e, err := input.Build() - if err != nil { - println("build") - s.fatalf(inputError(srcDir, err)) - } - - dep, err := e.Deployment() - - // TODO: This can be removed once application (#56) is implemented - if err == entity.ErrMissingContainer { - // check if has pod; if not deploy objects - pods, err := input.Entities(entity.EntityPod) - if err != nil && len(pods) != 0 { - s.fatalf("Failed to deploy: %v", err) - } - - dep, err = objectOnlyDeploy(input) + ref := c.Args().First() + var dep *deploy.Deployment + if len(ref) == 0 { + s.printf("Deploying from index...") + index, err := s.project().Index() if err != nil { - s.fatalf("Failed to deploy: %v", err) + s.fatalf("Error reading index: %v", err) + } + dep = index + } else { + if commit, err := s.project().ResolveCommit(ref); err == nil { + dep = commit + } else { + dep = s.fileDeploy(ref) } - - } else if err != nil { - println("deploy") - s.fatalf(inputError(srcDir, err)) } context := c.Args().Get(1) @@ -75,6 +56,40 @@ func (s SpreadCli) Deploy() *cli.Command { } } +func (s SpreadCli) fileDeploy(srcDir string) *deploy.Deployment { + input, err := dir.NewFileInput(srcDir) + if err != nil { + s.fatalf(inputError(srcDir, err)) + } + + e, err := input.Build() + if err != nil { + println("build") + s.fatalf(inputError(srcDir, err)) + } + + dep, err := e.Deployment() + + // TODO: This can be removed once application (#56) is implemented + if err == entity.ErrMissingContainer { + // check if has pod; if not deploy objects + pods, err := input.Entities(entity.EntityPod) + if err != nil && len(pods) != 0 { + s.fatalf("Failed to deploy: %v", err) + } + + dep, err = objectOnlyDeploy(input) + if err != nil { + s.fatalf("Failed to deploy: %v", err) + } + + } else if err != nil { + println("deploy") + s.fatalf(inputError(srcDir, err)) + } + return dep +} + func objectOnlyDeploy(input *dir.FileInput) (*deploy.Deployment, error) { objects, err := input.Objects() if err != nil { diff --git a/cli/diff.go b/cli/diff.go index b79ba36..3a25a81 100644 --- a/cli/diff.go +++ b/cli/diff.go @@ -6,12 +6,19 @@ import ( "rsprd.com/spread/pkg/deploy" ) -// Status returns information about the current state of the project. +// Diff shows the difference bettwen the cluster and the index. func (s SpreadCli) Diff() *cli.Command { return &cli.Command{ Name: "diff", Usage: "spread diff", Description: "Diffs index against state of cluster", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "context", + Value: "", + Usage: "kubectl context to use for requests", + }, + }, Action: func(c *cli.Context) { proj := s.project() index, err := proj.Index() @@ -19,7 +26,8 @@ func (s SpreadCli) Diff() *cli.Command { s.fatalf("Could not load Index: %v", err) } - client, err := deploy.NewKubeClusterFromContext("") + context := c.String("context") + client, err := deploy.NewKubeClusterFromContext(context) if err != nil { s.fatalf("Failed to connect to Kubernetes cluster: %v", err) } diff --git a/cli/status.go b/cli/status.go new file mode 100644 index 0000000..b512a4a --- /dev/null +++ b/cli/status.go @@ -0,0 +1,35 @@ +package cli + +import ( + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/deploy" +) + +// Status returns information about the current state of the project. +func (s SpreadCli) Status() *cli.Command { + return &cli.Command{ + Name: "status", + Usage: "spread status", + Description: "Information about what's commited, changed, and staged.", + Action: func(c *cli.Context) { + proj := s.project() + index, err := proj.Index() + if err != nil { + s.fatalf("Could not load Index: %v", err) + } + + client, err := deploy.NewKubeClusterFromContext("") + if err != nil { + s.fatalf("Failed to connect to Kubernetes cluster: %v", err) + } + + cluster, err := client.Deployment() + if err != nil { + s.fatalf("Could not load deployment from cluster: %v", err) + } + + s.printf(index.Diff(cluster)) + }, + } +} From 8fc1a64b66c92149fa189193fc59525eb26db25e Mon Sep 17 00:00:00 2001 From: Mackenzie Burnett Date: Fri, 17 Jun 2016 20:34:43 -0700 Subject: [PATCH 26/79] Update README with experimental versioning --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3ae7aa6..85963fc 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@

Website | Docs | Slack | Email | Twitter | Facebook

-#Spread: Docker to Kubernetes in one command +#Spread: Git for Kubernetes -`spread` is a command line tool that makes it easy to version Kubernetes objects (see: [Kit](https://github.com/redspread/kit)), set up a local Kubernetes cluster (see: [localkube](https://github.com/redspread/localkube)), and deploy to Kubernetes clusters in one command. The project's goals are to: +`spread` is a command line tool that makes it easy to version Kubernetes clusters, deploy to Kubernetes clusters in one command, and set up a local Kubernetes cluster (see: [localkube](https://github.com/redspread/localkube)). The project's goals are to: -* Enable rapid iteration with Kubernetes +* Guarantee reproducible Kubernetes deployments * Be the fastest, simplest way to deploy Docker to production -* Work well for a single developer or an entire team (no more broken bash scripts!) +* Enable collaborative deployment workflows that work well for one person or an entire team See how we deployed Mattermost (and you can too!): @@ -27,16 +27,53 @@ See our [philosophy](./philosophy.md) for more on our mission and values. ##Installation -Install with `go get`: +Install with `go get` (-d is for download only): -`go get rsprd.com/spread/cmd/spread` +`go get -d rsprd.com/spread/cmd/spread` + +Go into the correct directory: + +`cd $GOPATH/src/rsprd.com/spread` + +If libgit2 is not installed: + +`make install-libgit2` + +Then: + +`make build/spread` + +If an error about libraries missing comes up, set up your library path like: + +`export LD_LIBRARY_PATH=/usr/local/lib:$ LD_LIBRARY_PATH` Or, if you prefer using Homebrew (OS X only): -`$ brew tap redspread/spread` -`$ brew install spread` +`$ brew install spread-versioning` + +##Git for Kubernetes -##Quickstart +Spread versions your software environment (i.e. a Kubernetes cluster) like Git versions source code. Because Spread is built on top of libgit2, it takes advantage of Git's interface and functionality. This means after you deploy a Kubernetes object to a cluster, you can version the object by staging, commiting, and pushing it to a Spread repository. + +To get started, initialize Spread and set up a local Spread repository: + +`spread init` + +Here is our suggested workflow for versioning with Spread: + +1. Create or edit your Kubernetes objects +2. Deploy your objects to a local or remote Kubernetes cluster (make sure you've [set up your directory](https://github.com/redspread/spread/tree/versioning#faq) correctly): `spread deploy .` +3. Stage an object: `spread add /` +4. Repeat until all objects have been staged +5. Commit your objects with a message: `spread commit -m "commit message"` +6. Push your objects to a local Spread repository: `spread push remote local` +7. Your environment is now reproducible as a commit. + +Spread versioning is highly experimental for the next few weeks. If you find any bugs or have any feature requests for Spread versioning, please file an issue, and know that the format for Spread may change! + +For more details on Spread commands, [see our docs](https://redspread.readme.io/docs/spread-commands). + +##Spread Deploy Quickstart Check out our Getting Started Guide. @@ -66,6 +103,7 @@ Spread makes it easy to set up and iterate with [localkube](https://github.com/r - Iterate on your application, updating image and objects running `spread deploy .` each time you want to deploy changes - To preview changes, grab the IP of your docker daemon with `docker-machine env `and the returned `NodePort`, then put `IP:NodePort` in your browser - When finished, run `spread cluster stop` to stop localkube +- To remove the container entirely, run `spread cluster stop -r` [1] `spread` will soon integrate building ([#59](https://github.com/redspread/spread/issues/59)) [2] Since `localkube` shares a Docker daemon with your host, there is no need to push images :) @@ -74,26 +112,23 @@ Spread makes it easy to set up and iterate with [localkube](https://github.com/r ##What's been done so far +* Spread versioning * `spread deploy [-s] PATH [kubectl context]`: Deploys a Docker project to a Kubernetes cluster. It completes the following order of operations: * Reads context of directory and builds Kubernetes deployment hierarchy. * Updates all Kubernetes objects on a Kubernetes cluster. * Returns a public IP address, if type Load Balancer is specified. -* Established an implicit hierarchy of Kubernetes objects -* Multi-container deployment -* Support for Linux and Windows * [localkube](https://github.com/redspread/localkube): easy-to-setup local Kubernetes cluster for rapid development ##What's being worked on now -* Version control for Kubernetes objects * Inner-app linking +* Parameterization +* [Redspread](redspread.com) (hosted Spread repository) See more of our roadmap here! ##Future Goals * Peer-to-peer syncing between local and remote Kubernetes clusters -* Develop workflow for application and deployment versioning -* Introduce paramaterization for container configuration ##FAQ @@ -118,6 +153,8 @@ rs service.yaml secret.yaml ``` + + **Why version objects instead of just files?** The object is the deterministic representation of state in Kubernetes. A useful analogy is "Kubernetes objects" are to "Docker images" like "Kubernetes object files" are to "Dockerfiles". By versioning the object itself, we can guarantee a 1:1 mapping with the Kubernetes cluster. This allows us to do things like diff two clusters and introduces future potential for linking between objects and repositories. ##Contributing From 9a4c7674daa78a2247bbbd72783bf85c736642b9 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 20:50:37 -0700 Subject: [PATCH 27/79] added git status --- cli/status.go | 50 +++++++++++++++++++++++++++++++++++++++- pkg/deploy/deployment.go | 39 +++++++++++++++++++++++++++++++ pkg/deploy/kinds.go | 2 +- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/cli/status.go b/cli/status.go index b512a4a..7279cfc 100644 --- a/cli/status.go +++ b/cli/status.go @@ -19,6 +19,11 @@ func (s SpreadCli) Status() *cli.Command { s.fatalf("Could not load Index: %v", err) } + head, err := proj.Head() + if err != nil { + head = new(deploy.Deployment) + } + client, err := deploy.NewKubeClusterFromContext("") if err != nil { s.fatalf("Failed to connect to Kubernetes cluster: %v", err) @@ -29,7 +34,50 @@ func (s SpreadCli) Status() *cli.Command { s.fatalf("Could not load deployment from cluster: %v", err) } - s.printf(index.Diff(cluster)) + stat := deploy.Stat(index, head, cluster) + s.printStatus(stat) }, } } + +func (s SpreadCli) printStatus(status deploy.DiffStat) { + s.printf("From Index:") + if len(status.ClusterNew) > 0 { + s.printf("%d untracked objects:", len(status.ClusterNew)) + for _, path := range status.ClusterNew { + s.printf("- %s", path) + } + } + if len(status.ClusterModified) > 0 { + s.printf("%d modified objects:", len(status.ClusterModified)) + for _, path := range status.ClusterModified { + s.printf("- %s", path) + } + } + if len(status.ClusterDeleted) > 0 { + s.printf("%d deleted objects:", len(status.ClusterDeleted)) + for _, path := range status.ClusterDeleted { + s.printf("- %s", path) + } + } + + s.printf("From HEAD:") + if len(status.IndexNew) > 0 { + s.printf("%d staged creations:", len(status.IndexNew)) + for _, path := range status.IndexNew { + s.printf("- %s", path) + } + } + if len(status.IndexModified) > 0 { + s.printf("%d staged changes:", len(status.IndexModified)) + for _, path := range status.IndexModified { + s.printf("- %s", path) + } + } + if len(status.IndexDeleted) > 0 { + s.printf("%d staged deletions:", len(status.IndexDeleted)) + for _, path := range status.IndexDeleted { + s.printf("- %s", path) + } + } +} diff --git a/pkg/deploy/deployment.go b/pkg/deploy/deployment.go index 7cf8016..48df3e0 100644 --- a/pkg/deploy/deployment.go +++ b/pkg/deploy/deployment.go @@ -164,6 +164,45 @@ func (d *Deployment) Diff(other *Deployment) string { return out } +// PathDiff returns the list of the paths of objects. +// Currently doesn't detect modifications +func (d *Deployment) PathDiff(other *Deployment) (added, removed, modified []string) { + for path, obj := range d.objects { + if oObj, has := other.objects[path]; has { + if !kube.Semantic.DeepEqual(obj, oObj) { + //modified = append(modified, path) + } + + } else { + added = append(added, path) + } + } + + for path := range other.objects { + if _, has := d.objects[path]; !has { + removed = append(removed, path) + } + } + return +} + +// Stat returns change information about a deployment. +func Stat(index, head, cluster *Deployment) DiffStat { + stat := DiffStat{} + stat.IndexNew, stat.IndexDeleted, stat.IndexModified = index.PathDiff(head) + stat.ClusterNew, stat.ClusterDeleted, stat.ClusterModified = cluster.PathDiff(index) + return stat +} + +type DiffStat struct { + IndexNew []string + IndexModified []string + IndexDeleted []string + ClusterNew []string + ClusterModified []string + ClusterDeleted []string +} + // deepCopy creates a deep copy of the Kubernetes object given. func deepCopy(obj KubeObject) (KubeObject, error) { copy, err := kube.Scheme.DeepCopy(obj) diff --git a/pkg/deploy/kinds.go b/pkg/deploy/kinds.go index 098b84e..d0f3502 100644 --- a/pkg/deploy/kinds.go +++ b/pkg/deploy/kinds.go @@ -36,7 +36,7 @@ var kinds = map[string]KubeObject{ "configmap": &kube.ConfigMap{}, "serviceaccount": &kube.ServiceAccount{}, "componentstatus": &kube.ComponentStatus{}, - "endpoint": &kube.Endpoints{}, + "endpoints": &kube.Endpoints{}, "event": &kube.Event{}, "limitrange": &kube.LimitRange{}, "node": &kube.Node{}, From 3a2655518cbeccf44358208aca1bb5b6da251069 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 21:03:49 -0700 Subject: [PATCH 28/79] added git passthrough command --- cli/git.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 cli/git.go diff --git a/cli/git.go b/cli/git.go new file mode 100644 index 0000000..8fe49d2 --- /dev/null +++ b/cli/git.go @@ -0,0 +1,38 @@ +package cli + +import ( + "os/exec" + "path/filepath" + + "github.com/codegangsta/cli" +) + +func (s SpreadCli) Git() *cli.Command { + return &cli.Command{ + Name: "git", + Usage: "Allows access to git commands while Spread is build out", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "context", + Value: "", + Usage: "kubectl context to use for requests", + }, + }, + Action: func(c *cli.Context) { + proj := s.project() + gitDir := filepath.Join(proj.Path, "git") + + gitArgs := []string{"--git-dir=" + gitDir} + gitArgs = append(gitArgs, c.Args()...) + + cmd := exec.Command("git", gitArgs...) + cmd.Stdin = s.in + cmd.Stdout = s.out + cmd.Stderr = s.err + err := cmd.Run() + if err != nil { + s.fatalf("could not run git: %v", err) + } + }, + } +} From 541f7f8bbaf2ae6727ac2da5eb8b591b7f8bc02d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 17 Jun 2016 21:23:04 -0700 Subject: [PATCH 29/79] fix integration --- cli/add.go | 2 +- cli/cli.go | 18 +++++++++++++----- cli/commit.go | 2 +- cli/deploy.go | 27 ++++++++++++++++----------- cli/diff.go | 2 +- cli/git.go | 2 +- cli/status.go | 2 +- 7 files changed, 34 insertions(+), 21 deletions(-) diff --git a/cli/add.go b/cli/add.go index ce5ccac..e2f96f1 100644 --- a/cli/add.go +++ b/cli/add.go @@ -76,7 +76,7 @@ func (s SpreadCli) Add() *cli.Command { s.fatalf("failed to encode object: %v", err) } - proj := s.project() + proj := s.projectOrDie() err = proj.AddObjectToIndex(obj) if err != nil { s.fatalf("Failed to add object to Git index: %v", err) diff --git a/cli/cli.go b/cli/cli.go index f5daadc..0944799 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -38,21 +38,29 @@ func NewSpreadCli(in io.ReadCloser, out, err io.Writer, version, workDir string) } } -func (c SpreadCli) project() *project.Project { +func (c SpreadCli) projectOrDie() *project.Project { + proj, err := c.project() + if err != nil { + c.fatalf("%v", err) + } + return proj +} + +func (c SpreadCli) project() (*project.Project, error) { if len(c.workDir) == 0 { - c.fatalf("Encountered error: %v", ErrNoWorkDir) + return nil, fmt.Errorf("Encountered error: %v", ErrNoWorkDir) } root, found := findPath(c.workDir, project.SpreadDirectory, true) if !found { - c.fatalf("Not in a Spread project.") + return nil, fmt.Errorf("Not in a Spread project.") } proj, err := project.OpenProject(root) if err != nil { - c.fatalf("Error opening project: %v", err) + return nil, fmt.Errorf("Error opening project: %v", err) } - return proj + return proj, nil } func (c SpreadCli) printf(message string, data ...interface{}) { diff --git a/cli/commit.go b/cli/commit.go index a44a1bf..d010e4f 100644 --- a/cli/commit.go +++ b/cli/commit.go @@ -26,7 +26,7 @@ func (s SpreadCli) Commit() *cli.Command { s.fatalf("All commits must have a message. Specify one with 'spread commit -m \"message\"'") } - proj := s.project() + proj := s.projectOrDie() notImplemented := project.Person{ Name: "not implemented", Email: "not@implemented.com", diff --git a/cli/deploy.go b/cli/deploy.go index 8704fe2..c844165 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -21,19 +21,24 @@ func (s SpreadCli) Deploy() *cli.Command { Action: func(c *cli.Context) { ref := c.Args().First() var dep *deploy.Deployment - if len(ref) == 0 { - s.printf("Deploying from index...") - index, err := s.project().Index() - if err != nil { - s.fatalf("Error reading index: %v", err) - } - dep = index - } else { - if commit, err := s.project().ResolveCommit(ref); err == nil { - dep = commit + + if proj, err := s.project(); err == nil { + if len(ref) == 0 { + s.printf("Deploying from index...") + index, err := proj.Index() + if err != nil { + s.fatalf("Error reading index: %v", err) + } + dep = index } else { - dep = s.fileDeploy(ref) + if commit, err := proj.ResolveCommit(ref); err == nil { + dep = commit + } else { + dep = s.fileDeploy(ref) + } } + } else { + dep = s.fileDeploy(ref) } context := c.Args().Get(1) diff --git a/cli/diff.go b/cli/diff.go index 3a25a81..f333d27 100644 --- a/cli/diff.go +++ b/cli/diff.go @@ -20,7 +20,7 @@ func (s SpreadCli) Diff() *cli.Command { }, }, Action: func(c *cli.Context) { - proj := s.project() + proj := s.projectOrDie() index, err := proj.Index() if err != nil { s.fatalf("Could not load Index: %v", err) diff --git a/cli/git.go b/cli/git.go index 8fe49d2..530fae7 100644 --- a/cli/git.go +++ b/cli/git.go @@ -19,7 +19,7 @@ func (s SpreadCli) Git() *cli.Command { }, }, Action: func(c *cli.Context) { - proj := s.project() + proj := s.projectOrDie() gitDir := filepath.Join(proj.Path, "git") gitArgs := []string{"--git-dir=" + gitDir} diff --git a/cli/status.go b/cli/status.go index 7279cfc..be6ccee 100644 --- a/cli/status.go +++ b/cli/status.go @@ -13,7 +13,7 @@ func (s SpreadCli) Status() *cli.Command { Usage: "spread status", Description: "Information about what's commited, changed, and staged.", Action: func(c *cli.Context) { - proj := s.project() + proj := s.projectOrDie() index, err := proj.Index() if err != nil { s.fatalf("Could not load Index: %v", err) From 7696d62c99652a4e11216374843637083754ba5d Mon Sep 17 00:00:00 2001 From: Mackenzie Burnett Date: Fri, 17 Jun 2016 21:23:46 -0700 Subject: [PATCH 30/79] added brew tap --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 85963fc..4a1e39f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ If an error about libraries missing comes up, set up your library path like: Or, if you prefer using Homebrew (OS X only): +`$ brew tap redspread/spread` `$ brew install spread-versioning` ##Git for Kubernetes From 514da4341ae3080b607508b770ef8de0580f3979 Mon Sep 17 00:00:00 2001 From: Mackenzie Burnett Date: Fri, 17 Jun 2016 21:25:26 -0700 Subject: [PATCH 31/79] added line after brew tap --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a1e39f..6fd5c31 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ If an error about libraries missing comes up, set up your library path like: Or, if you prefer using Homebrew (OS X only): -`$ brew tap redspread/spread` +`$ brew tap redspread/spread` `$ brew install spread-versioning` ##Git for Kubernetes From 0da99fd0989c8c59c548df8e4dce2c95f8c85f60 Mon Sep 17 00:00:00 2001 From: Mackenzie Burnett Date: Fri, 17 Jun 2016 22:05:52 -0700 Subject: [PATCH 32/79] updated readme with more command instructions --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fd5c31..ec3961f 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,7 @@ Here is our suggested workflow for versioning with Spread: 3. Stage an object: `spread add /` 4. Repeat until all objects have been staged 5. Commit your objects with a message: `spread commit -m "commit message"` -6. Push your objects to a local Spread repository: `spread push remote local` -7. Your environment is now reproducible as a commit. +7. Go ahead and try out the other commands - anything not documented can be accessed using `spread git ...` Spread versioning is highly experimental for the next few weeks. If you find any bugs or have any feature requests for Spread versioning, please file an issue, and know that the format for Spread may change! From 86b531bf0acefa2fd03a61a2dcccc0f7faeddc0d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 30 Jun 2016 17:28:16 -0700 Subject: [PATCH 33/79] fixed git subcommand to allow arguments --- cli/git.go | 39 +++++++++++++++++++-------------------- cmd/spread/spread.go | 5 +++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/cli/git.go b/cli/git.go index 530fae7..4b990f1 100644 --- a/cli/git.go +++ b/cli/git.go @@ -3,6 +3,7 @@ package cli import ( "os/exec" "path/filepath" + "syscall" "github.com/codegangsta/cli" ) @@ -11,28 +12,26 @@ func (s SpreadCli) Git() *cli.Command { return &cli.Command{ Name: "git", Usage: "Allows access to git commands while Spread is build out", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "context", - Value: "", - Usage: "kubectl context to use for requests", - }, - }, Action: func(c *cli.Context) { - proj := s.projectOrDie() - gitDir := filepath.Join(proj.Path, "git") + cli.ShowSubcommandHelp(c) + }, + } +} - gitArgs := []string{"--git-dir=" + gitDir} - gitArgs = append(gitArgs, c.Args()...) +func (s SpreadCli) ExecGitCmd(args ...string) { + git, err := exec.LookPath("git") + if err != nil { + s.fatalf("Could not locate git: %v", err) + } - cmd := exec.Command("git", gitArgs...) - cmd.Stdin = s.in - cmd.Stdout = s.out - cmd.Stderr = s.err - err := cmd.Run() - if err != nil { - s.fatalf("could not run git: %v", err) - } - }, + proj := s.projectOrDie() + gitDir := filepath.Join(proj.Path, "git") + + gitArgs := []string{git, "--git-dir=" + gitDir} + gitArgs = append(gitArgs, args...) + + err = syscall.Exec(git, gitArgs, []string{}) + if err != nil { + s.fatalf("could not run git: %v", err) } } diff --git a/cmd/spread/spread.go b/cmd/spread/spread.go index 43217b6..464bfe3 100644 --- a/cmd/spread/spread.go +++ b/cmd/spread/spread.go @@ -13,6 +13,11 @@ func main() { wd, _ := os.Getwd() spread := cli.NewSpreadCli(os.Stdin, os.Stdout, os.Stderr, Version, wd) + // git override + if len(os.Args) > 2 && os.Args[1] == "git" { + spread.ExecGitCmd(os.Args[2:]...) + } + app := app() app.Commands = commands(spread) app.Run(os.Args) From 2f805716f658e3d8358cd160c02cee2b5f560719 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 30 Jun 2016 17:37:58 -0700 Subject: [PATCH 34/79] added command for creating & modifying remotes --- cli/remote.go | 96 ++++++++++++++++++++++++++++++++++++++++++ pkg/project/remotes.go | 9 ++++ 2 files changed, 105 insertions(+) create mode 100644 cli/remote.go create mode 100644 pkg/project/remotes.go diff --git a/cli/remote.go b/cli/remote.go new file mode 100644 index 0000000..3e38244 --- /dev/null +++ b/cli/remote.go @@ -0,0 +1,96 @@ +package cli + +import ( + "github.com/codegangsta/cli" +) + +// Remote manages the Git repositories remotes. +func (s SpreadCli) Remote() *cli.Command { + return &cli.Command{ + Name: "remote", + Usage: "View/modify versioning remotes", + Description: "Manages repository remotes.", + Action: func(c *cli.Context) { + p := s.projectOrDie() + remotes, err := p.Remotes().List() + if err != nil { + s.fatalf("couldn't retrieve remotes") + } + + s.printf("Remotes:") + for _, r := range remotes { + s.printf("- %s", r) + } + + println() + cli.ShowSubcommandHelp(c) + }, + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add new remote", + ArgsUsage: " ", + Action: func(c *cli.Context) { + name := c.Args().First() + if len(name) == 0 { + s.fatalf("a name must be specified") + } + + url := c.Args().Get(1) + if len(url) == 0 { + s.fatalf("a url must be specified") + } + + p := s.projectOrDie() + _, err := p.Remotes().Create(name, url) + if err != nil { + s.fatalf("Could not create new remote: %v", err) + } + + s.printf("Created remote '%s'", name) + }, + }, + { + Name: "remove", + Usage: "delete remote", + ArgsUsage: "", + Action: func(c *cli.Context) { + name := c.Args().First() + if len(name) == 0 { + s.fatalf("a name must be specified") + } + + p := s.projectOrDie() + if err := p.Remotes().Delete(name); err != nil { + s.fatalf("Could not delete remote: %v", err) + } + + s.printf("Removed remote '%s'", name) + }, + }, + { + Name: "set-url", + Usage: "change remotes url", + ArgsUsage: " ", + Action: func(c *cli.Context) { + name := c.Args().First() + if len(name) == 0 { + s.fatalf("a name must be specified") + } + + url := c.Args().Get(1) + if len(url) == 0 { + s.fatalf("a url must be specified") + } + + p := s.projectOrDie() + if err := p.Remotes().SetUrl(name, url); err != nil { + s.fatalf("Could not change url for remote: %v", err) + } + + s.printf("Set url for remote %s to '%s'", name, url) + }, + }, + }, + } +} diff --git a/pkg/project/remotes.go b/pkg/project/remotes.go new file mode 100644 index 0000000..3e9dcde --- /dev/null +++ b/pkg/project/remotes.go @@ -0,0 +1,9 @@ +package project + +import ( + git "gopkg.in/libgit2/git2go.v23" +) + +func (p *Project) Remotes() *git.RemoteCollection { + return &p.repo.Remotes +} From caacb4c5b82585584a58875761979cf48589f991 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 30 Jun 2016 18:40:50 -0700 Subject: [PATCH 35/79] added pushing --- cli/push.go | 32 ++++++++++++++++++++++++++++++++ pkg/project/remotes.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 cli/push.go diff --git a/cli/push.go b/cli/push.go new file mode 100644 index 0000000..6281ec5 --- /dev/null +++ b/cli/push.go @@ -0,0 +1,32 @@ +package cli + +import ( + "github.com/codegangsta/cli" +) + +// Push allows references to be pushed to a remote. +func (s SpreadCli) Push() *cli.Command { + return &cli.Command{ + Name: "push", + Usage: "Push references to a remote", + ArgsUsage: " ", + Description: "Push Spread data to a remote", + Action: func(c *cli.Context) { + remoteName := c.Args().First() + if len(remoteName) == 0 { + s.fatalf("a remote must be specified") + } + + if len(c.Args()) < 2 { + s.fatalf("a refspec must be specified") + } + refspec := c.Args()[1:] + + p := s.projectOrDie() + err := p.Push(remoteName, refspec...) + if err != nil { + s.fatalf("Failed to push: %v", err) + } + }, + } +} diff --git a/pkg/project/remotes.go b/pkg/project/remotes.go index 3e9dcde..74b9f43 100644 --- a/pkg/project/remotes.go +++ b/pkg/project/remotes.go @@ -1,9 +1,50 @@ package project import ( + "fmt" + + "github.com/mitchellh/go-homedir" + git "gopkg.in/libgit2/git2go.v23" ) +var remoteCallbacks = git.RemoteCallbacks{ + CredentialsCallback: func(url string, username_from_url string, allowed_types git.CredType) (git.ErrorCode, *git.Cred) { + pubKey, err := homedir.Expand("~/.ssh/id_rsa.pub") + if err != nil { + return git.ErrAuth, nil + } + + privKey, err := homedir.Expand("~/.ssh/id_rsa") + if err != nil { + return git.ErrAuth, nil + } + + code, key := git.NewCredSshKey("git", pubKey, privKey, "") + return git.ErrorCode(code), &key + }, + CertificateCheckCallback: func(cert *git.Certificate, valid bool, hostname string) git.ErrorCode { + // TODO: MAJOR SECURITY VULNERABILITY!!!!, resolve ASAP + return git.ErrOk + }, +} + func (p *Project) Remotes() *git.RemoteCollection { return &p.repo.Remotes } + +func (p *Project) Push(remoteName string, refspec ...string) error { + remote, err := p.Remotes().Lookup(remoteName) + if err != nil { + return fmt.Errorf("Failed to lookup branch: %v", err) + } + + pushOpts := &git.PushOptions{ + RemoteCallbacks: remoteCallbacks, + } + err = remote.Push(refspec, pushOpts) + if err != nil { + return fmt.Errorf("Failed to push: %v", err) + } + return nil +} From 4e8eb7876c5163785a6a6186c5adab48807967a8 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 30 Jun 2016 19:56:39 -0700 Subject: [PATCH 36/79] push fixes --- cli/push.go | 14 ++++++++++++-- pkg/project/remotes.go | 12 ++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cli/push.go b/cli/push.go index 6281ec5..d0dc54f 100644 --- a/cli/push.go +++ b/cli/push.go @@ -1,6 +1,9 @@ package cli import ( + "fmt" + "strings" + "github.com/codegangsta/cli" ) @@ -20,10 +23,17 @@ func (s SpreadCli) Push() *cli.Command { if len(c.Args()) < 2 { s.fatalf("a refspec must be specified") } - refspec := c.Args()[1:] + + refspecs := c.Args()[1:] + + for i, spec := range refspecs { + if !strings.HasPrefix(spec, "refs/") { + refspecs[i] = fmt.Sprintf("refs/heads/%s", spec) + } + } p := s.projectOrDie() - err := p.Push(remoteName, refspec...) + err := p.Push(remoteName, refspecs...) if err != nil { s.fatalf("Failed to push: %v", err) } diff --git a/pkg/project/remotes.go b/pkg/project/remotes.go index 74b9f43..e198d77 100644 --- a/pkg/project/remotes.go +++ b/pkg/project/remotes.go @@ -24,8 +24,12 @@ var remoteCallbacks = git.RemoteCallbacks{ return git.ErrorCode(code), &key }, CertificateCheckCallback: func(cert *git.Certificate, valid bool, hostname string) git.ErrorCode { - // TODO: MAJOR SECURITY VULNERABILITY!!!!, resolve ASAP - return git.ErrOk + if cert.Kind == git.CertificateHostkey { + return git.ErrOk + } else if valid { + return git.ErrOk + } + return git.ErrAuth }, } @@ -33,7 +37,7 @@ func (p *Project) Remotes() *git.RemoteCollection { return &p.repo.Remotes } -func (p *Project) Push(remoteName string, refspec ...string) error { +func (p *Project) Push(remoteName string, refspecs ...string) error { remote, err := p.Remotes().Lookup(remoteName) if err != nil { return fmt.Errorf("Failed to lookup branch: %v", err) @@ -42,7 +46,7 @@ func (p *Project) Push(remoteName string, refspec ...string) error { pushOpts := &git.PushOptions{ RemoteCallbacks: remoteCallbacks, } - err = remote.Push(refspec, pushOpts) + err = remote.Push(refspecs, pushOpts) if err != nil { return fmt.Errorf("Failed to push: %v", err) } From f9912ca1134aed7f7012ad900d6b57d86a7a8419 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 1 Jul 2016 14:41:48 -0700 Subject: [PATCH 37/79] first stab at pulling --- cli/pull.go | 38 ++++++++++++++++++++ pkg/project/commit.go | 26 +++++++++----- pkg/project/merge.go | 58 ++++++++++++++++++++++++++++++ pkg/project/pull.go | 80 ++++++++++++++++++++++++++++++++++++++++++ pkg/project/remotes.go | 22 ++++++++++-- 5 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 cli/pull.go create mode 100644 pkg/project/merge.go create mode 100644 pkg/project/pull.go diff --git a/cli/pull.go b/cli/pull.go new file mode 100644 index 0000000..4e6f025 --- /dev/null +++ b/cli/pull.go @@ -0,0 +1,38 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/codegangsta/cli" +) + +// Pull allows references to be pulled from a remote. +func (s SpreadCli) Pull() *cli.Command { + return &cli.Command{ + Name: "pull", + Usage: "Pull changes from a remote branch", + ArgsUsage: " ", + Description: "Pull Spread data from a remote branch", + Action: func(c *cli.Context) { + remoteName := c.Args().First() + if len(remoteName) == 0 { + s.fatalf("a remote must be specified") + } + + if len(c.Args()) < 2 { + s.fatalf("a refspec must be specified") + } + + refspec := c.Args().Get(1) + if !strings.HasPrefix(refspec, "refs/") { + refspec = fmt.Sprintf("refs/heads/%s", refspec) + } + + p := s.projectOrDie() + if err := p.Pull(remoteName, refspec); err != nil { + s.fatalf("Failed to pull: %v", err) + } + }, + } +} diff --git a/pkg/project/commit.go b/pkg/project/commit.go index 73a4039..dcbe406 100644 --- a/pkg/project/commit.go +++ b/pkg/project/commit.go @@ -16,27 +16,35 @@ func (p *Project) Commit(refname string, author, committer Person, message strin gitAuthor, gitCommitter := git.Signature(author), git.Signature(committer) - index, err := p.repo.Index() + commitTree, err := p.writeIndex() if err != nil { - return "", fmt.Errorf("could not get index: %v", err) + return "", err } - treeOid, err := index.WriteTree() + commit, err := p.repo.CreateCommit(refname, &gitAuthor, &gitCommitter, message, commitTree, parents...) if err != nil { - return "", fmt.Errorf("could not write index to tree: %v", err) + return "", fmt.Errorf("failed to create commit: %v", err) } - commitTree, err := p.repo.LookupTree(treeOid) + return commit.String(), nil +} + +func (p *Project) writeIndex() (*git.Tree, error) { + index, err := p.repo.Index() if err != nil { - return "", fmt.Errorf("could not retrieve created commit tree: %v", err) + return nil, fmt.Errorf("could not get index: %v", err) } - commit, err := p.repo.CreateCommit(refname, &gitAuthor, &gitCommitter, message, commitTree, parents...) + treeOid, err := index.WriteTree() if err != nil { - return "", fmt.Errorf("failed to create commit: %v", err) + return nil, fmt.Errorf("could not write index to tree: %v", err) } - return commit.String(), nil + tree, err := p.repo.LookupTree(treeOid) + if err != nil { + return nil, fmt.Errorf("could not retrieve created commit tree: %v", err) + } + return tree, nil } func (p *Project) Head() (*deploy.Deployment, error) { diff --git a/pkg/project/merge.go b/pkg/project/merge.go new file mode 100644 index 0000000..c17dc5a --- /dev/null +++ b/pkg/project/merge.go @@ -0,0 +1,58 @@ +package project + +import ( + "errors" + "time" + + git "gopkg.in/libgit2/git2go.v23" +) + +const ( + mergeName = "Merge Author" + mergeEmail = "merge@redspread.com" +) + +func (p *Project) merge(aCommits []*git.AnnotatedCommit, source, target *git.Reference) (*git.Oid, error) { + defer p.repo.StateCleanup() + + if err := p.repo.Merge(aCommits, nil, nil); err != nil { + return nil, err + } + + if index, err := p.repo.Index(); err != nil { + return nil, err + } else if index.HasConflicts() { + return nil, errors.New("Conflicts encountered during merge. Resolve them in the index.") + } + + // write index to disk and get ID + commitTree, err := p.writeIndex() + if err != nil { + return nil, err + } + + lCommit, err := p.repo.LookupCommit(target.Target()) + if err != nil { + return nil, err + } + + rCommit, err := p.repo.LookupCommit(source.Target()) + if err != nil { + return nil, err + } + + return p.repo.CreateCommit("HEAD", mergeSignature(), mergeSignature(), "Auto-merged changes", commitTree, lCommit, rCommit) +} + +func (p *Project) fastForward(source, target *git.Reference) error { + _, err := target.SetTarget(source.Target(), "Fast-forward") + return err +} + +func mergeSignature() *git.Signature { + return &git.Signature{ + Name: mergeName, + Email: mergeEmail, + When: time.Now(), + } +} diff --git a/pkg/project/pull.go b/pkg/project/pull.go new file mode 100644 index 0000000..90efb55 --- /dev/null +++ b/pkg/project/pull.go @@ -0,0 +1,80 @@ +package project + +import ( + "fmt" + "strings" + + git "gopkg.in/libgit2/git2go.v23" +) + +const ( + branchRef = "refs/heads/" +) + +// Pull fetches refspec from remoteName and merges them on top of HEAD. +func (p *Project) Pull(remoteName, refspec string) error { + // fetch from remote + if err := p.Fetch(remoteName, refspec); err != nil { + return err + } + + // if branch, use remote branch ref + branchName := "" + if strings.HasPrefix(refspec, branchRef) { + branchName = strings.TrimPrefix(refspec, branchRef) + refspec = fmt.Sprintf("refs/remotes/%s/%s", remoteName, branchName) + } + + ref, err := p.repo.References.Lookup(refspec) + if err != nil { + return err + } + + // get annotated commit of fetched branch's tip + aCommit, err := p.repo.AnnotatedCommitFromRef(ref) + if err != nil { + return err + } + + // Perform analysis of merge + mergeHeads := []*git.AnnotatedCommit{aCommit} + analysis, _, err := p.repo.MergeAnalysis(mergeHeads) + if err != nil { + return err + } + + head, err := p.repo.Head() + // create new branch for head if doesn't exist + if err != nil && strings.HasSuffix(err.Error(), "not found") { + branch := "master" + if len(branchName) != 0 { + branch = branchName + } + + commit, err := ref.Peel(git.ObjectCommit) + if err != nil { + return err + } + + if _, err = p.repo.CreateBranch(branch, commit.(*git.Commit), false); err != nil { + return err + } + + return p.repo.SetHead(fmt.Sprintf("refs/heads/%s", branch)) + } else if err != nil { + return err + } + + switch { + case analysis&git.MergeAnalysisUpToDate != 0: + // no changes required + return nil + case analysis&git.MergeAnalysisNormal != 0: + _, err = p.merge(mergeHeads, ref, head) + return err + case analysis&git.MergeAnalysisFastForward != 0: + return p.fastForward(ref, head) + } + + return fmt.Errorf("merge analysis failed to determine a viable strategy, result: %d", analysis) +} diff --git a/pkg/project/remotes.go b/pkg/project/remotes.go index e198d77..b018e46 100644 --- a/pkg/project/remotes.go +++ b/pkg/project/remotes.go @@ -43,12 +43,30 @@ func (p *Project) Push(remoteName string, refspecs ...string) error { return fmt.Errorf("Failed to lookup branch: %v", err) } - pushOpts := &git.PushOptions{ + opts := &git.PushOptions{ RemoteCallbacks: remoteCallbacks, } - err = remote.Push(refspecs, pushOpts) + err = remote.Push(refspecs, opts) if err != nil { return fmt.Errorf("Failed to push: %v", err) } return nil } + +func (p *Project) Fetch(remoteName string, refspecs ...string) error { + remote, err := p.Remotes().Lookup(remoteName) + if err != nil { + return fmt.Errorf("Failed to lookup branch: %v", err) + } + + opts := &git.FetchOptions{ + RemoteCallbacks: remoteCallbacks, + } + + // fetch with default reflog message + err = remote.Fetch(refspecs, opts, "") + if err != nil { + return fmt.Errorf("Failed to fetch: %v", err) + } + return nil +} From a33c0369ba4271c54eb60965ead2e5f3af67625d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 5 Jul 2016 13:37:14 -0700 Subject: [PATCH 38/79] working pull --- pkg/project/pull.go | 24 +++++++++++++++--------- pkg/project/workdir.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 pkg/project/workdir.go diff --git a/pkg/project/pull.go b/pkg/project/pull.go index 90efb55..2a8a24f 100644 --- a/pkg/project/pull.go +++ b/pkg/project/pull.go @@ -36,13 +36,6 @@ func (p *Project) Pull(remoteName, refspec string) error { return err } - // Perform analysis of merge - mergeHeads := []*git.AnnotatedCommit{aCommit} - analysis, _, err := p.repo.MergeAnalysis(mergeHeads) - if err != nil { - return err - } - head, err := p.repo.Head() // create new branch for head if doesn't exist if err != nil && strings.HasSuffix(err.Error(), "not found") { @@ -65,15 +58,28 @@ func (p *Project) Pull(remoteName, refspec string) error { return err } + path, err := p.TempWorkdir() + if err != nil { + return fmt.Errorf("could not setup temporary working directory: %v", err) + } + defer p.CleanupWorkdir(path) + + // Perform analysis of merge + mergeHeads := []*git.AnnotatedCommit{aCommit} + analysis, _, err := p.repo.MergeAnalysis(mergeHeads) + if err != nil { + return err + } + switch { case analysis&git.MergeAnalysisUpToDate != 0: // no changes required return nil + case analysis&git.MergeAnalysisFastForward != 0: + return p.fastForward(ref, head) case analysis&git.MergeAnalysisNormal != 0: _, err = p.merge(mergeHeads, ref, head) return err - case analysis&git.MergeAnalysisFastForward != 0: - return p.fastForward(ref, head) } return fmt.Errorf("merge analysis failed to determine a viable strategy, result: %d", analysis) diff --git a/pkg/project/workdir.go b/pkg/project/workdir.go new file mode 100644 index 0000000..54f078a --- /dev/null +++ b/pkg/project/workdir.go @@ -0,0 +1,37 @@ +package project + +import ( + "io/ioutil" + "os" + + git "gopkg.in/libgit2/git2go.v23" +) + +// TempWorkdir creates a temporary directory and configures the Repository to use it as a work dir. +// HEAD is checked out to the temporary working directory. The path of the working directory is returned as a string. +func (p *Project) TempWorkdir() (string, error) { + name, err := ioutil.TempDir("", "spread-workdir") + if err != nil { + return "", err + } + + opts := &git.CheckoutOpts{ + TargetDirectory: name, + } + + if err = p.repo.CheckoutHead(opts); err != nil { + os.RemoveAll(name) + return "", err + } + + return name, p.repo.SetWorkdir(name, false) +} + +// CleanupWorkdir removes the given directory and sets the repositories Workdir to an empty string. +func (p *Project) CleanupWorkdir(dir string) error { + if err := os.RemoveAll(dir); err != nil { + return err + } + + return p.repo.SetWorkdir("", false) +} From 89699e1125255e82d1183b10986542fafafbb277 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 6 Jul 2016 00:54:26 -0700 Subject: [PATCH 39/79] First stab at new schema --- Makefile | 7 + pkg/data/decode.go | 45 +-- pkg/data/encode.go | 83 ++--- pkg/data/object.go | 12 +- pkg/spreadproto/object.pb.go | 567 ++++++++++++++++++----------------- proto/object.proto | 61 ++++ 6 files changed, 409 insertions(+), 366 deletions(-) create mode 100644 proto/object.proto diff --git a/Makefile b/Makefile index 9bea546..fb5160d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ BASE := rsprd.com/spread DIR := $(GOPATH)/src/$(BASE) CMD_NAME := spread +PROTO_DIR := proto EXEC_PKG := $(BASE)/cmd/$(CMD_NAME) PKGS := ./pkg/... ./cli/... ./cmd/... @@ -20,6 +21,7 @@ GOX ?= gox GOFMT ?= gofmt "-s" GOLINT ?= golint DOCKER ?= docker +PROTOC := protoc GOFILES := find . -name '*.go' -not -path "./vendor/*" @@ -80,6 +82,11 @@ integration: build mkdir -p ./build ./test/mattermost-demo.sh +proto: pkg/spreadproto/*.pb.go +pkg/spreadproto/*.pb.go:$(PROTO_DIR)/*.proto + mkdir -p $$(dirname $@) + $(PROTOC) --go_out=plugins=grpc:$$(dirname $@) -I=$(PROTO_DIR) $^ + .PHONY: validate validate: lint checkgofmt vet diff --git a/pkg/data/decode.go b/pkg/data/decode.go index fe3dd26..73a2422 100644 --- a/pkg/data/decode.go +++ b/pkg/data/decode.go @@ -2,7 +2,6 @@ package data import ( "fmt" - "strconv" pb "rsprd.com/spread/pkg/spreadproto" ) @@ -10,50 +9,38 @@ import ( func decodeField(field *pb.Field) (interface{}, error) { val := field.GetValue() if val == nil { - return nil, fmt.Errorf("value for '%s' was nil", field.Key) + return nil, nil } - switch val.Type { - case pb.FieldValue_NUMBER: - return strconv.ParseFloat(val.Value, 64) - case pb.FieldValue_STRING: - return val.Value, nil - case pb.FieldValue_BOOL: - return strconv.ParseBool(val.Value) - case pb.FieldValue_NULL: - return nil, nil - case pb.FieldValue_MAP: - return decodeMapField(field) - case pb.FieldValue_ARRAY: - return decodeArrayField(field) + switch v := val.(type) { + case *pb.Field_Number: + return v.Number, nil + case *pb.Field_Str: + return v.Str, nil + case *pb.Field_Boolean: + return v.Boolean, nil + case *pb.Field_Obj: + return decodeMap(v.Obj.Item) + case *pb.Field_Array: + return decodeArray(v.Array.GetItems()) } return nil, fmt.Errorf("unknown type for Field '%s'", field.Key) } -func decodeMapField(root *pb.Field) (map[string]interface{}, error) { - fields := root.GetFields() - if fields == nil { - return nil, nil - } - +func decodeMap(fields map[string]*pb.Field) (map[string]interface{}, error) { out := make(map[string]interface{}, len(fields)) - for _, field := range fields { + for k, field := range fields { val, err := decodeField(field) if err != nil { return nil, fmt.Errorf("couldn't decode '%s': %v", field.Key, err) } - out[field.Key] = val + out[k] = val } return out, nil } -func decodeArrayField(root *pb.Field) ([]interface{}, error) { - fields := root.GetFields() - if fields == nil { - return nil, nil - } - +func decodeArray(fields []*pb.Field) ([]interface{}, error) { out := make([]interface{}, len(fields)) for i, field := range fields { val, err := decodeField(field) diff --git a/pkg/data/encode.go b/pkg/data/encode.go index f1ca230..5d45b30 100644 --- a/pkg/data/encode.go +++ b/pkg/data/encode.go @@ -7,63 +7,38 @@ import ( ) func buildField(key string, data interface{}) (*pb.Field, error) { + field := &pb.Field{ + Key: key, + } + + // don't set Value for nil if data == nil { - return buildNil(key) + return field, nil } switch typedData := data.(type) { case bool: - return buildBool(key, typedData) + field.Value = &pb.Field_Boolean{ + Boolean: typedData, + } case float64: - return buildNumber(key, typedData) + field.Value = &pb.Field_Number{ + Number: typedData, + } case string: - return buildString(key, typedData) + field.Value = &pb.Field_Str{ + Str: typedData, + } case []interface{}: return buildArray(key, typedData) case map[string]interface{}: return buildMap(key, typedData) } - return nil, fmt.Errorf("could not resolve type of %s (value=%+v)", key, data) -} - -func buildNil(key string) (*pb.Field, error) { - return &pb.Field{ - Key: key, - Value: &pb.FieldValue{ - Type: pb.FieldValue_NULL, - }, - }, nil -} - -func buildBool(key string, data bool) (*pb.Field, error) { - return &pb.Field{ - Key: key, - Value: &pb.FieldValue{ - Type: pb.FieldValue_BOOL, - Value: fmt.Sprintf("%t", data), - }, - }, nil -} - -func buildNumber(key string, data float64) (*pb.Field, error) { - return &pb.Field{ - Key: key, - Value: &pb.FieldValue{ - Type: pb.FieldValue_NUMBER, - Value: fmt.Sprintf("%g", data), - }, - }, nil -} - -func buildString(key string, data string) (*pb.Field, error) { - return &pb.Field{ - Key: key, - Value: &pb.FieldValue{ - Type: pb.FieldValue_STRING, - Value: data, - }, - }, nil + if field.Value == nil { + return nil, fmt.Errorf("could not resolve type of %s (value=%+v)", key, data) + } + return field, nil } func buildArray(key string, data []interface{}) (*pb.Field, error) { @@ -79,30 +54,30 @@ func buildArray(key string, data []interface{}) (*pb.Field, error) { return &pb.Field{ Key: key, - Value: &pb.FieldValue{ - Type: pb.FieldValue_ARRAY, + Value: &pb.Field_Array{ + Array: &pb.Array{ + Items: arr, + }, }, - Fields: arr, }, nil } func buildMap(key string, data map[string]interface{}) (*pb.Field, error) { - arr := make([]*pb.Field, len(data)) - i := 0 + obj := make(map[string]*pb.Field, len(data)) for k, v := range data { field, err := buildField(k, v) if err != nil { return nil, err } - arr[i] = field - i++ + obj[k] = field } return &pb.Field{ Key: key, - Value: &pb.FieldValue{ - Type: pb.FieldValue_MAP, + Value: &pb.Field_Obj{ + Obj: &pb.Map{ + Item: obj, + }, }, - Fields: arr, }, nil } diff --git a/pkg/data/object.go b/pkg/data/object.go index 1ce2bb7..076af19 100644 --- a/pkg/data/object.go +++ b/pkg/data/object.go @@ -36,13 +36,15 @@ func ObjectFromMap(name, path string, data map[string]interface{}) (*pb.Object, } i := 0 - obj.Fields = make([]*pb.Field, len(data)) + obj.Fields = &pb.Array{ + Items: make([]*pb.Field, len(data)), + } for k, v := range data { field, err := buildField(k, v) if err != nil { return nil, err } - obj.Fields[i] = field + obj.Fields.Items[i] = field i++ } return obj, nil @@ -65,12 +67,12 @@ func Unmarshal(obj *pb.Object, ptr interface{}) error { func MapFromObject(obj *pb.Object) (map[string]interface{}, error) { fields := obj.GetFields() - if fields == nil { + if fields == nil || fields.GetItems() == nil { return nil, ErrObjectNilFields } - out := make(map[string]interface{}, len(fields)) - for _, field := range fields { + out := make(map[string]interface{}, len(fields.GetItems())) + for _, field := range fields.GetItems() { val, err := decodeField(field) if err != nil { return nil, fmt.Errorf("could not decode field '%s': %v", field.Key, err) diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index 609e264..2dfb87d 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -2,6 +2,21 @@ // source: object.proto // DO NOT EDIT! +/* +Package spreadproto is a generated protocol buffer package. + +It is generated from these files: + object.proto + +It has these top-level messages: + Field + Map + Array + SRI + Link + Object + ObjectInfo +*/ package spreadproto import proto "github.com/golang/protobuf/proto" @@ -13,303 +28,323 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -type Delta_Status int32 - -const ( - Delta_ADDED Delta_Status = 0 - Delta_DELETED Delta_Status = 1 - Delta_MODIFIED Delta_Status = 2 - Delta_RENAMED Delta_Status = 3 - Delta_COPIED Delta_Status = 4 - Delta_IGNORED Delta_Status = 5 - Delta_UNTRACKED Delta_Status = 6 -) - -var Delta_Status_name = map[int32]string{ - 0: "ADDED", - 1: "DELETED", - 2: "MODIFIED", - 3: "RENAMED", - 4: "COPIED", - 5: "IGNORED", - 6: "UNTRACKED", -} -var Delta_Status_value = map[string]int32{ - "ADDED": 0, - "DELETED": 1, - "MODIFIED": 2, - "RENAMED": 3, - "COPIED": 4, - "IGNORED": 5, - "UNTRACKED": 6, -} - -func (x Delta_Status) String() string { - return proto.EnumName(Delta_Status_name, int32(x)) -} -func (Delta_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{0, 0} } - -type DiffField_Status int32 - -const ( - DiffField_ADDED DiffField_Status = 0 - DiffField_CHANGED DiffField_Status = 1 - DiffField_REMOVED DiffField_Status = 2 -) - -var DiffField_Status_name = map[int32]string{ - 0: "ADDED", - 1: "CHANGED", - 2: "REMOVED", -} -var DiffField_Status_value = map[string]int32{ - "ADDED": 0, - "CHANGED": 1, - "REMOVED": 2, -} - -func (x DiffField_Status) String() string { - return proto.EnumName(DiffField_Status_name, int32(x)) -} -func (DiffField_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{3, 0} } - -type FieldValue_Type int32 - -const ( - FieldValue_NUMBER FieldValue_Type = 0 - FieldValue_STRING FieldValue_Type = 1 - FieldValue_BOOL FieldValue_Type = 2 - FieldValue_MAP FieldValue_Type = 3 - FieldValue_ARRAY FieldValue_Type = 4 - FieldValue_NULL FieldValue_Type = 5 -) - -var FieldValue_Type_name = map[int32]string{ - 0: "NUMBER", - 1: "STRING", - 2: "BOOL", - 3: "MAP", - 4: "ARRAY", - 5: "NULL", -} -var FieldValue_Type_value = map[string]int32{ - "NUMBER": 0, - "STRING": 1, - "BOOL": 2, - "MAP": 3, - "ARRAY": 4, - "NULL": 5, -} - -func (x FieldValue_Type) String() string { - return proto.EnumName(FieldValue_Type_name, int32(x)) -} -func (FieldValue_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{6, 0} } - -// Delta is the difference between two objects. -type Delta struct { - Status Delta_Status `protobuf:"varint,1,opt,name=status,enum=spread.Delta_Status" json:"status,omitempty"` - Summary *DeltaSummary `protobuf:"bytes,2,opt,name=summary" json:"summary,omitempty"` - OldName string `protobuf:"bytes,3,opt,name=oldName" json:"oldName,omitempty"` - NewName string `protobuf:"bytes,4,opt,name=newName" json:"newName,omitempty"` - Fields []*DiffField `protobuf:"bytes,5,rep,name=fields" json:"fields,omitempty"` -} - -func (m *Delta) Reset() { *m = Delta{} } -func (m *Delta) String() string { return proto.CompactTextString(m) } -func (*Delta) ProtoMessage() {} -func (*Delta) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } - -func (m *Delta) GetSummary() *DeltaSummary { - if m != nil { - return m.Summary - } - return nil -} +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 -func (m *Delta) GetFields() []*DiffField { - if m != nil { - return m.Fields - } - return nil +// Field represents a field of an object. +type Field struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + // Types that are valid to be assigned to Value: + // *Field_Number + // *Field_Str + // *Field_Boolean + // *Field_Obj + // *Field_Array + // *Field_Link + Value isField_Value `protobuf_oneof:"value"` } -// DeltaSummary provides a summary of changes in a delta. -type DeltaSummary struct { - Added int64 `protobuf:"varint,1,opt,name=added" json:"added,omitempty"` - Changed int64 `protobuf:"varint,2,opt,name=changed" json:"changed,omitempty"` - Removed int64 `protobuf:"varint,3,opt,name=removed" json:"removed,omitempty"` -} +func (m *Field) Reset() { *m = Field{} } +func (m *Field) String() string { return proto.CompactTextString(m) } +func (*Field) ProtoMessage() {} +func (*Field) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } -func (m *DeltaSummary) Reset() { *m = DeltaSummary{} } -func (m *DeltaSummary) String() string { return proto.CompactTextString(m) } -func (*DeltaSummary) ProtoMessage() {} -func (*DeltaSummary) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} } +type isField_Value interface { + isField_Value() +} -// Diff represents the difference between two sets of objects. -type Diff struct { - Stats *DiffStats `protobuf:"bytes,1,opt,name=stats" json:"stats,omitempty"` - Deltas []*Delta `protobuf:"bytes,2,rep,name=deltas" json:"deltas,omitempty"` +type Field_Number struct { + Number float64 `protobuf:"fixed64,2,opt,name=number,oneof"` +} +type Field_Str struct { + Str string `protobuf:"bytes,3,opt,name=str,oneof"` +} +type Field_Boolean struct { + Boolean bool `protobuf:"varint,4,opt,name=boolean,oneof"` +} +type Field_Obj struct { + Obj *Map `protobuf:"bytes,5,opt,name=obj,oneof"` +} +type Field_Array struct { + Array *Array `protobuf:"bytes,6,opt,name=array,oneof"` +} +type Field_Link struct { + Link *Link `protobuf:"bytes,7,opt,name=link,oneof"` } -func (m *Diff) Reset() { *m = Diff{} } -func (m *Diff) String() string { return proto.CompactTextString(m) } -func (*Diff) ProtoMessage() {} -func (*Diff) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} } +func (*Field_Number) isField_Value() {} +func (*Field_Str) isField_Value() {} +func (*Field_Boolean) isField_Value() {} +func (*Field_Obj) isField_Value() {} +func (*Field_Array) isField_Value() {} +func (*Field_Link) isField_Value() {} -func (m *Diff) GetStats() *DiffStats { +func (m *Field) GetValue() isField_Value { if m != nil { - return m.Stats + return m.Value } return nil } -func (m *Diff) GetDeltas() []*Delta { - if m != nil { - return m.Deltas +func (m *Field) GetNumber() float64 { + if x, ok := m.GetValue().(*Field_Number); ok { + return x.Number } - return nil + return 0 } -// DiffField represents the difference between two fields. -type DiffField struct { - Status DiffField_Status `protobuf:"varint,1,opt,name=status,enum=spread.DiffField_Status" json:"status,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` - OldValue *FieldValue `protobuf:"bytes,3,opt,name=oldValue" json:"oldValue,omitempty"` - NewValue *FieldValue `protobuf:"bytes,4,opt,name=newValue" json:"newValue,omitempty"` - OldLink *Link `protobuf:"bytes,5,opt,name=oldLink" json:"oldLink,omitempty"` - NewLink *Link `protobuf:"bytes,6,opt,name=newLink" json:"newLink,omitempty"` - LinkChanged bool `protobuf:"varint,7,opt,name=linkChanged" json:"linkChanged,omitempty"` - Children []*DiffField `protobuf:"bytes,8,rep,name=children" json:"children,omitempty"` +func (m *Field) GetStr() string { + if x, ok := m.GetValue().(*Field_Str); ok { + return x.Str + } + return "" } -func (m *DiffField) Reset() { *m = DiffField{} } -func (m *DiffField) String() string { return proto.CompactTextString(m) } -func (*DiffField) ProtoMessage() {} -func (*DiffField) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} } +func (m *Field) GetBoolean() bool { + if x, ok := m.GetValue().(*Field_Boolean); ok { + return x.Boolean + } + return false +} -func (m *DiffField) GetOldValue() *FieldValue { - if m != nil { - return m.OldValue +func (m *Field) GetObj() *Map { + if x, ok := m.GetValue().(*Field_Obj); ok { + return x.Obj } return nil } -func (m *DiffField) GetNewValue() *FieldValue { - if m != nil { - return m.NewValue +func (m *Field) GetArray() *Array { + if x, ok := m.GetValue().(*Field_Array); ok { + return x.Array } return nil } -func (m *DiffField) GetOldLink() *Link { - if m != nil { - return m.OldLink +func (m *Field) GetLink() *Link { + if x, ok := m.GetValue().(*Field_Link); ok { + return x.Link } return nil } -func (m *DiffField) GetNewLink() *Link { - if m != nil { - return m.NewLink +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Field) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Field_OneofMarshaler, _Field_OneofUnmarshaler, _Field_OneofSizer, []interface{}{ + (*Field_Number)(nil), + (*Field_Str)(nil), + (*Field_Boolean)(nil), + (*Field_Obj)(nil), + (*Field_Array)(nil), + (*Field_Link)(nil), } - return nil } -func (m *DiffField) GetChildren() []*DiffField { - if m != nil { - return m.Children +func _Field_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Field) + // value + switch x := m.Value.(type) { + case *Field_Number: + b.EncodeVarint(2<<3 | proto.WireFixed64) + b.EncodeFixed64(math.Float64bits(x.Number)) + case *Field_Str: + b.EncodeVarint(3<<3 | proto.WireBytes) + b.EncodeStringBytes(x.Str) + case *Field_Boolean: + t := uint64(0) + if x.Boolean { + t = 1 + } + b.EncodeVarint(4<<3 | proto.WireVarint) + b.EncodeVarint(t) + case *Field_Obj: + b.EncodeVarint(5<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Obj); err != nil { + return err + } + case *Field_Array: + b.EncodeVarint(6<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Array); err != nil { + return err + } + case *Field_Link: + b.EncodeVarint(7<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Link); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Field.Value has unexpected type %T", x) } return nil } -// DiffStats provides a summary of the operations in a diff. -type DiffStats struct { - ObjectsChanged int64 `protobuf:"varint,1,opt,name=objectsChanged" json:"objectsChanged,omitempty"` - Insertions int64 `protobuf:"varint,2,opt,name=insertions" json:"insertions,omitempty"` - Deletions int64 `protobuf:"varint,3,opt,name=deletions" json:"deletions,omitempty"` - Renames int64 `protobuf:"varint,4,opt,name=renames" json:"renames,omitempty"` +func _Field_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Field) + switch tag { + case 2: // value.number + if wire != proto.WireFixed64 { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeFixed64() + m.Value = &Field_Number{math.Float64frombits(x)} + return true, err + case 3: // value.str + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Value = &Field_Str{x} + return true, err + case 4: // value.boolean + if wire != proto.WireVarint { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeVarint() + m.Value = &Field_Boolean{x != 0} + return true, err + case 5: // value.obj + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Map) + err := b.DecodeMessage(msg) + m.Value = &Field_Obj{msg} + return true, err + case 6: // value.array + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Array) + err := b.DecodeMessage(msg) + m.Value = &Field_Array{msg} + return true, err + case 7: // value.link + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Link) + err := b.DecodeMessage(msg) + m.Value = &Field_Link{msg} + return true, err + default: + return false, nil + } } -func (m *DiffStats) Reset() { *m = DiffStats{} } -func (m *DiffStats) String() string { return proto.CompactTextString(m) } -func (*DiffStats) ProtoMessage() {} -func (*DiffStats) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{4} } +func _Field_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Field) + // value + switch x := m.Value.(type) { + case *Field_Number: + n += proto.SizeVarint(2<<3 | proto.WireFixed64) + n += 8 + case *Field_Str: + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.Str))) + n += len(x.Str) + case *Field_Boolean: + n += proto.SizeVarint(4<<3 | proto.WireVarint) + n += 1 + case *Field_Obj: + s := proto.Size(x.Obj) + n += proto.SizeVarint(5<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Field_Array: + s := proto.Size(x.Array) + n += proto.SizeVarint(6<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Field_Link: + s := proto.Size(x.Link) + n += proto.SizeVarint(7<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} -// Field represents a field of an object. -type Field struct { - Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` - Value *FieldValue `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` - Link *Link `protobuf:"bytes,3,opt,name=link" json:"link,omitempty"` - Fields []*Field `protobuf:"bytes,4,rep,name=fields" json:"fields,omitempty"` +// Map represents a map. +type Map struct { + Item map[string]*Field `protobuf:"bytes,1,rep,name=item" json:"item,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` } -func (m *Field) Reset() { *m = Field{} } -func (m *Field) String() string { return proto.CompactTextString(m) } -func (*Field) ProtoMessage() {} -func (*Field) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} } +func (m *Map) Reset() { *m = Map{} } +func (m *Map) String() string { return proto.CompactTextString(m) } +func (*Map) ProtoMessage() {} +func (*Map) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } -func (m *Field) GetValue() *FieldValue { +func (m *Map) GetItem() map[string]*Field { if m != nil { - return m.Value + return m.Item } return nil } -func (m *Field) GetLink() *Link { - if m != nil { - return m.Link - } - return nil +// Array represents an array. +type Array struct { + Items []*Field `protobuf:"bytes,1,rep,name=items" json:"items,omitempty"` } -func (m *Field) GetFields() []*Field { +func (m *Array) Reset() { *m = Array{} } +func (m *Array) String() string { return proto.CompactTextString(m) } +func (*Array) ProtoMessage() {} +func (*Array) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *Array) GetItems() []*Field { if m != nil { - return m.Fields + return m.Items } return nil } -// FieldValue represents the value of a field. -type FieldValue struct { - Type FieldValue_Type `protobuf:"varint,1,opt,name=type,enum=spread.FieldValue_Type" json:"type,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` - Link string `protobuf:"bytes,3,opt,name=link" json:"link,omitempty"` +// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. +type SRI struct { + Treeish string `protobuf:"bytes,1,opt,name=treeish" json:"treeish,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"` + Field string `protobuf:"bytes,3,opt,name=field" json:"field,omitempty"` } -func (m *FieldValue) Reset() { *m = FieldValue{} } -func (m *FieldValue) String() string { return proto.CompactTextString(m) } -func (*FieldValue) ProtoMessage() {} -func (*FieldValue) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{6} } +func (m *SRI) Reset() { *m = SRI{} } +func (m *SRI) String() string { return proto.CompactTextString(m) } +func (*SRI) ProtoMessage() {} +func (*SRI) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } // Link represents a relationship to another field. type Link struct { - // fullIRI is the full representation on an IRI. An IRI is the JSON-LD equivalent of an URL. This string contains the information found the in the following fields. - FullIRI string `protobuf:"bytes,1,opt,name=fullIRI" json:"fullIRI,omitempty"` - // repo is the Redspread repository being linked to - Repo string `protobuf:"bytes,2,opt,name=repo" json:"repo,omitempty"` - // oid is the Git object id of the commit or tree being linked to - Oid string `protobuf:"bytes,3,opt,name=oid" json:"oid,omitempty"` - // path is full name of the object within a repository - Path string `protobuf:"bytes,4,opt,name=path" json:"path,omitempty"` + PackageName string `protobuf:"bytes,1,opt,name=packageName" json:"packageName,omitempty"` + Target *SRI `protobuf:"bytes,2,opt,name=target" json:"target,omitempty"` + Override bool `protobuf:"varint,3,opt,name=override" json:"override,omitempty"` } func (m *Link) Reset() { *m = Link{} } func (m *Link) String() string { return proto.CompactTextString(m) } func (*Link) ProtoMessage() {} -func (*Link) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} } +func (*Link) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *Link) GetTarget() *SRI { + if m != nil { + return m.Target + } + return nil +} // Object holds a representation of a Kubernetes object. type Object struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Info *ObjectInfo `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` - Fields []*Field `protobuf:"bytes,3,rep,name=fields" json:"fields,omitempty"` + Fields *Array `protobuf:"bytes,3,opt,name=fields" json:"fields,omitempty"` } func (m *Object) Reset() { *m = Object{} } func (m *Object) String() string { return proto.CompactTextString(m) } func (*Object) ProtoMessage() {} -func (*Object) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} } +func (*Object) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } func (m *Object) GetInfo() *ObjectInfo { if m != nil { @@ -318,7 +353,7 @@ func (m *Object) GetInfo() *ObjectInfo { return nil } -func (m *Object) GetFields() []*Field { +func (m *Object) GetFields() *Array { if m != nil { return m.Fields } @@ -333,66 +368,42 @@ type ObjectInfo struct { func (m *ObjectInfo) Reset() { *m = ObjectInfo{} } func (m *ObjectInfo) String() string { return proto.CompactTextString(m) } func (*ObjectInfo) ProtoMessage() {} -func (*ObjectInfo) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} } +func (*ObjectInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } func init() { - proto.RegisterType((*Delta)(nil), "spread.Delta") - proto.RegisterType((*DeltaSummary)(nil), "spread.DeltaSummary") - proto.RegisterType((*Diff)(nil), "spread.Diff") - proto.RegisterType((*DiffField)(nil), "spread.DiffField") - proto.RegisterType((*DiffStats)(nil), "spread.DiffStats") proto.RegisterType((*Field)(nil), "spread.Field") - proto.RegisterType((*FieldValue)(nil), "spread.FieldValue") + proto.RegisterType((*Map)(nil), "spread.Map") + proto.RegisterType((*Array)(nil), "spread.Array") + proto.RegisterType((*SRI)(nil), "spread.SRI") proto.RegisterType((*Link)(nil), "spread.Link") proto.RegisterType((*Object)(nil), "spread.Object") proto.RegisterType((*ObjectInfo)(nil), "spread.ObjectInfo") - proto.RegisterEnum("spread.Delta_Status", Delta_Status_name, Delta_Status_value) - proto.RegisterEnum("spread.DiffField_Status", DiffField_Status_name, DiffField_Status_value) - proto.RegisterEnum("spread.FieldValue_Type", FieldValue_Type_name, FieldValue_Type_value) -} - -var fileDescriptor2 = []byte{ - // 661 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x54, 0xdb, 0x6e, 0xd3, 0x40, - 0x10, 0xc5, 0xf1, 0x25, 0xf6, 0x38, 0x29, 0xed, 0x82, 0xc0, 0xaa, 0x54, 0x29, 0x18, 0x2a, 0xf5, - 0x85, 0x3c, 0x94, 0x0f, 0x00, 0x37, 0x4e, 0x83, 0x45, 0x62, 0x57, 0x6e, 0x5a, 0x04, 0x3c, 0xb9, - 0xf1, 0x86, 0x9a, 0x3a, 0x76, 0x64, 0x3b, 0x45, 0xfd, 0x13, 0x7e, 0x84, 0xdf, 0x43, 0xcc, 0xee, - 0xda, 0xbd, 0xa4, 0xcd, 0x93, 0xb3, 0x73, 0xce, 0xce, 0xec, 0x9c, 0x33, 0x13, 0xe8, 0xe4, 0x17, - 0xbf, 0xe8, 0xac, 0xea, 0x2f, 0x8b, 0xbc, 0xca, 0x89, 0x56, 0x2e, 0x0b, 0x1a, 0xc5, 0xf6, 0x3f, - 0x09, 0x54, 0x97, 0xa6, 0x55, 0x44, 0xde, 0x81, 0x56, 0x56, 0x51, 0xb5, 0x2a, 0x2d, 0xa9, 0x27, - 0x1d, 0x6c, 0x1d, 0xbe, 0xec, 0x0b, 0x4a, 0x9f, 0xc3, 0xfd, 0x53, 0x8e, 0x91, 0x7d, 0x68, 0x97, - 0xab, 0xc5, 0x22, 0x2a, 0x6e, 0xac, 0x16, 0xd2, 0xcc, 0x35, 0xda, 0xa9, 0xc0, 0xc8, 0x73, 0x68, - 0xe7, 0x69, 0xec, 0x47, 0x0b, 0x6a, 0xc9, 0x48, 0x33, 0x58, 0x20, 0xa3, 0xbf, 0x79, 0x40, 0xe1, - 0x81, 0x37, 0xa0, 0xcd, 0x13, 0x9a, 0xc6, 0xa5, 0xa5, 0xf6, 0x64, 0xcc, 0xb3, 0x73, 0x9b, 0x27, - 0x99, 0xcf, 0x8f, 0x19, 0x62, 0xcf, 0x40, 0xab, 0xab, 0x1a, 0xa0, 0x3a, 0xae, 0x3b, 0x74, 0xb7, - 0x9f, 0x11, 0x13, 0xda, 0xee, 0x70, 0x3c, 0x9c, 0xe2, 0x41, 0x22, 0x1d, 0xd0, 0x27, 0x81, 0xeb, - 0x1d, 0x7b, 0x78, 0x6a, 0x31, 0x28, 0x1c, 0xfa, 0xce, 0x04, 0x0f, 0x32, 0x01, 0xd0, 0x06, 0xc1, - 0x09, 0x03, 0x14, 0x06, 0x78, 0x23, 0x3f, 0x08, 0xf1, 0xa0, 0x92, 0x2e, 0x18, 0x67, 0xfe, 0x34, - 0x74, 0x06, 0x5f, 0xf0, 0xa8, 0xd9, 0x1f, 0xa1, 0xf3, 0xe0, 0xe5, 0x5d, 0x50, 0xa3, 0x38, 0xa6, - 0x31, 0x57, 0x41, 0x66, 0xef, 0x9e, 0x5d, 0x46, 0xd9, 0x4f, 0x0c, 0xb4, 0x9a, 0x40, 0x41, 0x17, - 0xf9, 0x35, 0x06, 0x58, 0x67, 0xb2, 0x3d, 0x02, 0x85, 0x3d, 0x99, 0xf4, 0x40, 0x65, 0xfa, 0x09, - 0xf9, 0xd6, 0xfa, 0x61, 0x6d, 0x94, 0x64, 0x0f, 0xb4, 0x98, 0x95, 0x2a, 0x31, 0x15, 0x6b, 0xb9, - 0xfb, 0x40, 0x3a, 0xfb, 0x6f, 0x0b, 0x8c, 0xdb, 0xe6, 0xc9, 0xc1, 0x9a, 0x1d, 0xd6, 0x23, 0x7d, - 0x1a, 0x4b, 0x4c, 0x90, 0xaf, 0xa8, 0xb0, 0xc3, 0x40, 0x17, 0x75, 0x14, 0xfe, 0x3c, 0x4a, 0x57, - 0x42, 0x79, 0xf3, 0x90, 0x34, 0x17, 0xf9, 0x25, 0x8e, 0x30, 0x16, 0xba, 0x21, 0x58, 0xca, 0x46, - 0xd6, 0x1e, 0x37, 0x71, 0x9c, 0x64, 0x57, 0xe8, 0x11, 0x23, 0x75, 0x1a, 0x12, 0x8b, 0x31, 0x18, - 0x93, 0x70, 0x58, 0x7b, 0x02, 0x7e, 0x01, 0x66, 0x8a, 0xdf, 0x41, 0xad, 0x5e, 0x1b, 0x29, 0x3a, - 0x79, 0x0b, 0xfa, 0xec, 0x32, 0x49, 0xe3, 0x82, 0x66, 0x96, 0xbe, 0xc9, 0xf7, 0xf7, 0x1b, 0x7c, - 0x1f, 0x7c, 0x76, 0xfc, 0x11, 0xf7, 0x9d, 0x3b, 0x3d, 0x09, 0xce, 0x99, 0xed, 0xf6, 0x0f, 0x21, - 0x9b, 0xd0, 0xf8, 0x15, 0x6c, 0x89, 0x39, 0x2f, 0x9b, 0xc2, 0xc2, 0x47, 0x9c, 0x87, 0x24, 0x2b, - 0x69, 0x51, 0x25, 0x79, 0x56, 0xd6, 0x56, 0xee, 0x80, 0x81, 0x7e, 0x50, 0x11, 0x92, 0xef, 0xdc, - 0xcd, 0x70, 0x4a, 0x4b, 0xae, 0x8b, 0x6c, 0x17, 0xa0, 0x0a, 0x3f, 0x6a, 0x95, 0xa5, 0x7a, 0x78, - 0xd5, 0x6b, 0x2e, 0x5e, 0x6b, 0xa3, 0x78, 0xbb, 0xa0, 0xb0, 0xf6, 0x6b, 0x13, 0xd6, 0x95, 0x6b, - 0x66, 0x5f, 0x79, 0x38, 0x08, 0xa2, 0xff, 0x3f, 0x12, 0xc0, 0xbd, 0x4c, 0xfb, 0xa0, 0x54, 0x37, - 0x4b, 0x5a, 0xcf, 0xc1, 0xeb, 0xc7, 0xb5, 0xfa, 0x53, 0x84, 0xd9, 0xe0, 0xde, 0xbd, 0xc9, 0xc0, - 0xd5, 0xb8, 0xab, 0x6f, 0xd8, 0xc7, 0xa0, 0x70, 0x12, 0x6e, 0x85, 0x7f, 0x36, 0x39, 0x1a, 0x86, - 0xa8, 0x28, 0xfe, 0x3e, 0x9d, 0x86, 0x9e, 0x3f, 0x42, 0x41, 0x75, 0x50, 0x8e, 0x82, 0x60, 0x8c, - 0x4b, 0xd4, 0x06, 0x79, 0xe2, 0x9c, 0xe0, 0x02, 0x31, 0xed, 0xc3, 0xd0, 0xf9, 0x86, 0xfb, 0x83, - 0xa8, 0x7f, 0x36, 0x1e, 0x6f, 0xab, 0xf6, 0x27, 0x50, 0x78, 0x07, 0xa8, 0xd3, 0x7c, 0x95, 0xa6, - 0x5e, 0xe8, 0xd5, 0x8a, 0x60, 0xb9, 0x82, 0x2e, 0xf3, 0xba, 0x38, 0x8a, 0x95, 0x27, 0x71, 0xbd, - 0xfa, 0x08, 0x2d, 0xa3, 0xea, 0x52, 0xec, 0xbd, 0xfd, 0x15, 0xb4, 0x80, 0x1b, 0xc4, 0xe2, 0x4c, - 0xe9, 0x3a, 0x41, 0x0f, 0x94, 0x24, 0x9b, 0xe7, 0xeb, 0x8a, 0x0a, 0xae, 0x87, 0xc8, 0x3d, 0xd5, - 0xe4, 0xa7, 0x54, 0xdb, 0x05, 0xb8, 0x47, 0x6e, 0x8a, 0xf2, 0xe4, 0x47, 0xdd, 0xef, 0xa6, 0xe0, - 0xf2, 0x3f, 0xbf, 0x0b, 0x8d, 0x7f, 0x3e, 0xfc, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x46, 0xe4, 0x99, - 0xf9, 0x13, 0x05, 0x00, 0x00, +} + +var fileDescriptor0 = []byte{ + // 381 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x5c, 0x52, 0x4d, 0x6b, 0xe2, 0x50, + 0x14, 0x35, 0xe6, 0x4b, 0x6f, 0x94, 0x71, 0xde, 0x30, 0x10, 0x1c, 0x67, 0x90, 0x0c, 0x03, 0xb3, + 0xca, 0xa2, 0x6e, 0x8a, 0xbb, 0x0a, 0x2d, 0x5a, 0xfa, 0x01, 0xed, 0xa2, 0xd0, 0xdd, 0x8b, 0x5e, + 0x35, 0x35, 0x26, 0xe1, 0xe5, 0x29, 0xf8, 0x9b, 0xfa, 0x27, 0x7b, 0xdf, 0x4b, 0x62, 0xb5, 0x2b, + 0xb9, 0xf7, 0x1c, 0xcf, 0x39, 0xf7, 0xbc, 0x40, 0x27, 0x8b, 0xde, 0x70, 0x2e, 0xc3, 0x5c, 0x64, + 0x32, 0x63, 0x4e, 0x91, 0x0b, 0xe4, 0x8b, 0xe0, 0xdd, 0x00, 0xfb, 0x26, 0xc6, 0x64, 0xc1, 0x3c, + 0x30, 0x37, 0x78, 0xf0, 0x8d, 0xa1, 0xf1, 0xbf, 0xcd, 0x7a, 0xe0, 0xa4, 0xbb, 0x6d, 0x84, 0xc2, + 0x6f, 0xd2, 0x6c, 0x4c, 0x1b, 0xac, 0x0b, 0x66, 0x21, 0x85, 0x6f, 0x2a, 0x98, 0xc6, 0xef, 0xe0, + 0x46, 0x59, 0x96, 0x20, 0x4f, 0x7d, 0x8b, 0x56, 0x2d, 0x5a, 0xf5, 0xc1, 0x24, 0x0b, 0xdf, 0xa6, + 0xd1, 0xbb, 0xf0, 0xc2, 0xd2, 0x20, 0xbc, 0xe7, 0x39, 0x61, 0x7f, 0xc0, 0xe6, 0x42, 0xf0, 0x83, + 0xef, 0x68, 0xb4, 0x5b, 0xa3, 0x57, 0x6a, 0x49, 0xf8, 0x00, 0xac, 0x24, 0x4e, 0x37, 0xbe, 0xab, + 0xe1, 0x4e, 0x0d, 0xdf, 0xd1, 0x6e, 0xda, 0x98, 0xb8, 0x60, 0xef, 0x79, 0xb2, 0xc3, 0x60, 0x09, + 0x26, 0xe9, 0xb1, 0xbf, 0x60, 0xc5, 0x12, 0xb7, 0x94, 0xd5, 0x24, 0xf6, 0xcf, 0x13, 0xab, 0x70, + 0x46, 0xfb, 0xeb, 0x54, 0x8a, 0x43, 0x7f, 0x0c, 0xed, 0xe3, 0x70, 0x7e, 0xdc, 0xa0, 0x92, 0xd3, + 0xb7, 0x9d, 0x84, 0xd1, 0x3d, 0x8c, 0x9b, 0x97, 0x46, 0xf0, 0x0f, 0x6c, 0x9d, 0x4c, 0x51, 0x95, + 0x53, 0x51, 0x59, 0x9d, 0x53, 0x83, 0x11, 0x98, 0xcf, 0x4f, 0x33, 0xf6, 0x0d, 0x5c, 0x29, 0x10, + 0xe3, 0x62, 0x5d, 0x19, 0x74, 0xc0, 0xca, 0xb9, 0x5c, 0x6b, 0xfd, 0x36, 0x35, 0x67, 0x2f, 0x15, + 0xbd, 0xec, 0x2e, 0xb8, 0x05, 0x4b, 0x9d, 0xc5, 0x7e, 0x80, 0x97, 0xf3, 0xf9, 0x86, 0xaf, 0xf0, + 0x81, 0x6f, 0xb1, 0xfa, 0xe7, 0x2f, 0x70, 0x24, 0x17, 0x2b, 0x94, 0x55, 0xb6, 0x63, 0x8d, 0xca, + 0xa7, 0x07, 0xad, 0x6c, 0x8f, 0x42, 0xc4, 0x0b, 0xd4, 0x5a, 0xad, 0xe0, 0x05, 0x9c, 0x47, 0xfd, + 0xaa, 0xca, 0x32, 0xfd, 0x94, 0x19, 0x52, 0x41, 0xe9, 0x32, 0xab, 0x44, 0x58, 0x2d, 0x52, 0x72, + 0x67, 0x84, 0xb0, 0xdf, 0xe0, 0xe8, 0x50, 0x85, 0x56, 0xfa, 0xfa, 0x22, 0x41, 0x1f, 0xe0, 0x84, + 0x5c, 0xdf, 0xa3, 0xc5, 0x27, 0xdd, 0x57, 0xaf, 0xe4, 0xea, 0x2f, 0x29, 0x72, 0xf4, 0xcf, 0xe8, + 0x23, 0x00, 0x00, 0xff, 0xff, 0x1c, 0x4d, 0xd5, 0x03, 0x60, 0x02, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto new file mode 100644 index 0000000..94adf0e --- /dev/null +++ b/proto/object.proto @@ -0,0 +1,61 @@ +// Copyright 2016, Redspread Inc. +// All rights reserved. + +syntax = "proto3"; + +package spread; + +option go_package = "spreadproto"; + +/////////////////// +// Models / +/////////////////// + +// Field represents a field of an object. +message Field { + string key = 1; + oneof value { + double number = 2; + string str = 3; + bool boolean = 4; + Map obj = 5; + Array array = 6; + Link link = 7; + } +} + +// Map represents a map. +message Map { + map item = 1; +} + +// Array represents an array. +message Array { + repeated Field items = 1; +} + +// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. +message SRI { + string treeish = 1; + string path = 2; + string field = 3; +} + +// Link represents a relationship to another field. +message Link { + string packageName = 1; + SRI target = 2; + bool override = 3; +} + +// Object holds a representation of a Kubernetes object. +message Object { + string name = 1; + ObjectInfo info = 2; + repeated Field fields = 3; +} + +// ObjectInfo provides metadata about an object. +message ObjectInfo { + string path = 1; +} From f0037f1a86a3b9685bab949a3fbd77c3a145e0a9 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 28 Jun 2016 11:59:53 -0700 Subject: [PATCH 40/79] wrote SRL tests --- pkg/data/srl.go | 49 +++++++++++ pkg/data/srl_test.go | 190 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 pkg/data/srl.go create mode 100644 pkg/data/srl_test.go diff --git a/pkg/data/srl.go b/pkg/data/srl.go new file mode 100644 index 0000000..31ff673 --- /dev/null +++ b/pkg/data/srl.go @@ -0,0 +1,49 @@ +package data + +import ( + "fmt" +) + +const ( + MaxObjectIDLen = 40 + MinObjectIDLen = 7 +) + +// A SRL represents a parsed Spread Resource Locator (SRL), a globally unique address for an object or field stored within a repository. +// This is represented as: +// +// treeish/path[?fieldpath] +type SRL struct { + // Treeish is a Git Object ID to either a Commit or a Tree Git object. + // The use of a Git OID (treeish) allows for any object or field to be addressed regardless if it is accessible. + // The Object ID may be truncated down to a minimum of 7 characters. + Treeish string + + // Path to the Spread Object being addressed. If omitted, SRL refers to treeish. + // This will be traversed starting from the given Treeish. + Path string + + // Field specifies a path to the field within the Object that is being referred to. If omitted, the SRL refers to the entire object. + // Path must be given to have a Field. + // Fieldpaths are specified by name using the character “.” to specify sub-fields. + // Fieldpath of arrays are addressed using their 0 indexed position wrapped with parentheses. + // The use of parentheses is due to restrictions in the syntax of URLs. + Field string +} + +func (s *SRL) String() string { + return "" +} + +// ParseSRL parses rawsrl into SRL struct. +func ParseSRL(rawsrl string) (*SRL, error) { + return nil, nil +} + +var ( + // ErrOIDTooLong is returned when the length of the Git ObjectID is above MaxObjectIDLen + ErrOIDTooLong = fmt.Errorf("git object ID was too long, must be %d chars at most.", MaxObjectIDLen) + + // ErrOIDTooShort is returned when the length of the Git ObjectID is below MinObjectIDLen + ErrOIDTooShort = fmt.Errorf("git object ID was too short, must be at least %d chars.", MinObjectIDLen) +) diff --git a/pkg/data/srl_test.go b/pkg/data/srl_test.go new file mode 100644 index 0000000..ae1ca12 --- /dev/null +++ b/pkg/data/srl_test.go @@ -0,0 +1,190 @@ +package data + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +type SRLTest struct { + in string + out *SRL // nil if error + outStr string // expected string for success, prefix of error for failure +} + +var goodSRLs = []SRLTest{ + // treeish only + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + &SRL{ + Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + }, + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + }, + // treeish only, with slash + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/", + &SRL{ + Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + }, + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + }, + // shortened treeish + { + "a434f0b", + &SRL{ + Treeish: "a434f0b", + }, + "a434f0b", + }, + // treeish & path + { + "e8f3ab9/default/replicationcontroller/web/", + &SRL{ + Treeish: "e8f3ab9", + Path: "default/replicationcontroller/web", + }, + "e8f3ab9/default/replicationcontroller/web", + }, + // full SRL (no Field) + { + "e8f3ab9/default/replicationcontroller/web/?", + &SRL{ + Treeish: "e8f3ab9", + Path: "default/replicationcontroller/web", + }, + "e8f3ab9/default/replicationcontroller/web", + }, + // full SRL + { + "e8f3ab9/default/replicationcontroller/web?spec.template.spec.containers(0)", + &SRL{ + Treeish: "e8f3ab9", + Path: "default/replicationcontroller/web", + Field: "spec.template.spec.containers(0)", + }, + "e8f3ab9/default/replicationcontroller/web?spec.template.spec.containers(0)", + }, + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web/?spec.template.spec.containers(0)", + &SRL{ + Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + Path: "default/replicationcontroller/web", + Field: "spec.template.spec.containers(0)", + }, + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web?spec.template.spec.containers(0)", + }, + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9//default//replicationcontroller//web//?spec.template.spec.containers(0)", + &SRL{ + Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + Path: "default/replicationcontroller/web", + Field: "spec.template.spec.containers(0)", + }, + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web?spec.template.spec.containers(0)", + }, + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web/?spec.template.spec.containers(0)(1)", + &SRL{ + Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + Path: "default/replicationcontroller/web", + Field: "spec.template.spec.containers(0)", + }, + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web?spec.template.spec.containers(0)(1)", + }, +} + +func sfmt(s *SRL) string { + if s == nil { + s = new(SRL) + } + return fmt.Sprintf("treeish=%s, path=%s, field=%s", s.Treeish, s.Path, s.Field) +} + +func TestParseGoodSRLs(t *testing.T) { + for i, test := range goodSRLs { + srl, err := ParseSRL(test.in) + if err != nil { + t.Errorf("%s(%d) failed with: %v", test.in, i, err) + } else if !reflect.DeepEqual(srl, test.out) { + t.Errorf("%s(%d):\n\thave %v\n\twant %v\n", test.in, i, sfmt(srl), sfmt(test.out)) + } else if srl.String() != test.outStr { + t.Errorf("%s(%d) - Bad Serialization:\n\thave %s\n\twant %s\n", test.in, i, srl.String(), test.outStr) + } + } +} + +var badSRLs = []SRLTest{ + // empty string + { + "", + nil, + "git object ID was too short", + }, + // short OID + { + "a434f", + nil, + "git object ID was too short", + }, + // invalid characters in ID + { + "a343invalidID", + nil, + "invalid Treeish", + }, + // invalid characters in path + { + "e8f3ab9/default/replication controller/web/spec.template.spec.containers(0)", + nil, + "invalid Path", + }, + // invalid characters in field + { + "e8f3ab9/default/replicationcontroller/web/spec.tem&&&&plate.spec.containers(0)", + nil, + "invalid Field", + }, + // character in field array access + { + "e8f3ab9/default/replicationcontroller/web/spec.template.spec.containers(d)", + nil, + "invalid Field", + }, + // double field dot + { + "e8f3ab9/default/replicationcontroller/web/spec..template.spec.containers(0)", + nil, + "invalid Field", + }, + // start field with dot + { + "e8f3ab9/default/replicationcontroller/web/.spec.template.spec.containers(0)", + nil, + "invalid Field", + }, + // unclosed parentheses + { + "e8f3ab9/default/replicationcontroller/web/spec.template.spec.containers(", + nil, + "invalid Field", + }, + // unopened parentheses + { + "e8f3ab9/default/replicationcontroller/web/spec.template).spec.containers", + nil, + "invalid Field", + }, +} + +func TestParseBadSRLs(t *testing.T) { + for i, test := range badSRLs { + _, err := ParseSRL(test.in) + if err == nil { + t.Errorf("%s(%d) did not return error (expected error prefix: %s)", test.in, i, test.outStr) + } else if !strings.HasPrefix(err.Error(), test.outStr) { + t.Errorf("%s(%d) wrong error (expected error prefix: %s)", test.in, i, test.outStr) + } + } +} From 4fda5ab8d2c939230cd73f674678266886d8ab27 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 28 Jun 2016 14:29:40 -0700 Subject: [PATCH 41/79] implemented SRLs --- pkg/data/srl.go | 158 ++++++++++++++++++++++++++++++++++++++++--- pkg/data/srl_test.go | 90 +++++++++++++++++++++--- 2 files changed, 231 insertions(+), 17 deletions(-) diff --git a/pkg/data/srl.go b/pkg/data/srl.go index 31ff673..c059263 100644 --- a/pkg/data/srl.go +++ b/pkg/data/srl.go @@ -1,12 +1,25 @@ package data import ( + "errors" "fmt" + "path/filepath" + "regexp" + "strings" + "unicode" ) const ( MaxObjectIDLen = 40 MinObjectIDLen = 7 + ValidOIDChars = "abcdef0123456789" + + OIDRegex = `[^a-f0-9]+` + PathRegex = `[^a-zA-Z0-9./]+` + FieldRegex = `[^a-zA-Z0-9./()]+` + + PathDelimiter = "/" + FieldDelimiter = "?" ) // A SRL represents a parsed Spread Resource Locator (SRL), a globally unique address for an object or field stored within a repository. @@ -31,19 +44,148 @@ type SRL struct { Field string } +// String returns a textual representation of the SRL which will be similar to the input. func (s *SRL) String() string { - return "" + str := s.Treeish + if len(s.Path) > 0 { + str += "/" + s.Path + } + if len(s.Field) > 0 { + str += "?" + s.Field + } + return str } // ParseSRL parses rawsrl into SRL struct. func ParseSRL(rawsrl string) (*SRL, error) { - return nil, nil + oid, path, field := parts(rawsrl) + var err error + if oid, err = ParseOID(oid); err != nil { + return nil, err + } + + if path, err = ParsePath(path); err != nil { + return nil, err + } + + if field, err = ParseField(field); err != nil { + return nil, err + } + + return &SRL{ + Treeish: oid, + Path: path, + Field: field, + }, nil } -var ( - // ErrOIDTooLong is returned when the length of the Git ObjectID is above MaxObjectIDLen - ErrOIDTooLong = fmt.Errorf("git object ID was too long, must be %d chars at most.", MaxObjectIDLen) +func parts(rawsrl string) (oid, path, field string) { + if len(rawsrl) == 0 { + return + } - // ErrOIDTooShort is returned when the length of the Git ObjectID is below MinObjectIDLen - ErrOIDTooShort = fmt.Errorf("git object ID was too short, must be at least %d chars.", MinObjectIDLen) -) + // OID + pathDelim := strings.Index(rawsrl, PathDelimiter) + // check if only OID + if pathDelim == -1 { + oid = rawsrl + return + } + oid = rawsrl[:pathDelim] + + // Path + fieldDelim := strings.LastIndex(rawsrl, FieldDelimiter) + if fieldDelim == -1 { + if len(rawsrl) > pathDelim+1 { + path = rawsrl[pathDelim+1:] + } + return + } + + path, field = rawsrl[pathDelim+1:fieldDelim], rawsrl[fieldDelim+1:] + return +} + +func ParseOID(oidStr string) (string, error) { + if len(oidStr) < MinObjectIDLen { + return "", fmt.Errorf("git object ID was too short (%d chars), must be at least %d chars.", len(oidStr), MinObjectIDLen) + } else if len(oidStr) > MaxObjectIDLen { + return "", fmt.Errorf("git object ID was too long (%d chars), must be %d chars at most.", len(oidStr), MaxObjectIDLen) + } + + // check has valid chars + if regexp.MustCompile(OIDRegex).MatchString(oidStr) { + return "", fmt.Errorf("invalid Treeish, invalid character in '%s' (only can contain '%s')", oidStr, ValidOIDChars) + } + return oidStr, nil +} + +func ParsePath(pathStr string) (string, error) { + if len(pathStr) == 0 { + return "", nil + } + + // check has valid chars + if regexp.MustCompile(PathRegex).MatchString(pathStr) { + return "", fmt.Errorf("invalid Path, invalid character in '%s' (must match regex '%s')", pathStr, PathRegex) + } + + pathStr = filepath.Clean(pathStr) + if pathStr[0] == '/' { + if len(pathStr) == 1 { + return "", nil + } + pathStr = pathStr[1:] + } + return pathStr, nil +} + +func ParseField(fieldStr string) (string, error) { + if len(fieldStr) == 0 { + return "", nil + } + + if fieldStr[0] == '.' { + return "", errors.New("invalid Field: cannot begin with '.'") + } + + if strings.Contains(fieldStr, "..") { + return "", errors.New("invalid Field: cannot repeat '.'") + } + + // check has valid chars + if regexp.MustCompile(FieldRegex).MatchString(fieldStr) { + return "", fmt.Errorf("invalid Field, invalid character in '%s' (must match regex '%s')", fieldStr, FieldRegex) + } + + if err := checkIllegalParens(fieldStr); err != nil { + return "", fmt.Errorf("invalid Field: %v", err) + } + + return fieldStr, nil +} + +func checkIllegalParens(fieldStr string) error { + inParen := false + for _, c := range fieldStr { + if c == '(' { + if inParen { + return errors.New("already opened parentheses") + } + inParen = true + } else if c == ')' { + if !inParen { + return errors.New("closed parenthese when one hasn't been opened") + } + inParen = false + } else if inParen && !unicode.IsNumber(c) { + return errors.New("only numeric characters can be used in parentheses") + } + } + + if inParen { + return errors.New("unclosed parentheses") + } + + return nil +} diff --git a/pkg/data/srl_test.go b/pkg/data/srl_test.go index ae1ca12..a561f64 100644 --- a/pkg/data/srl_test.go +++ b/pkg/data/srl_test.go @@ -89,7 +89,7 @@ var goodSRLs = []SRLTest{ &SRL{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", Path: "default/replicationcontroller/web", - Field: "spec.template.spec.containers(0)", + Field: "spec.template.spec.containers(0)(1)", }, "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web?spec.template.spec.containers(0)(1)", }, @@ -136,43 +136,43 @@ var badSRLs = []SRLTest{ }, // invalid characters in path { - "e8f3ab9/default/replication controller/web/spec.template.spec.containers(0)", + "e8f3ab9/default/replication controller/web?spec.template.spec.containers(0)", nil, "invalid Path", }, // invalid characters in field { - "e8f3ab9/default/replicationcontroller/web/spec.tem&&&&plate.spec.containers(0)", + "e8f3ab9/default/replicationcontroller/web?spec.tem&&&&plate.spec.containers(0)", nil, "invalid Field", }, // character in field array access { - "e8f3ab9/default/replicationcontroller/web/spec.template.spec.containers(d)", + "e8f3ab9/default/replicationcontroller/web/?spec.template.spec.containers(d)", nil, "invalid Field", }, // double field dot { - "e8f3ab9/default/replicationcontroller/web/spec..template.spec.containers(0)", + "e8f3ab9/default/replicationcontroller/web/?spec..template.spec.containers(0)", nil, "invalid Field", }, // start field with dot { - "e8f3ab9/default/replicationcontroller/web/.spec.template.spec.containers(0)", + "e8f3ab9/default/replicationcontroller/web?.spec.template.spec.containers(0)", nil, "invalid Field", }, // unclosed parentheses { - "e8f3ab9/default/replicationcontroller/web/spec.template.spec.containers(", + "e8f3ab9/default/replicationcontroller/web?spec.template.spec.containers(", nil, "invalid Field", }, // unopened parentheses { - "e8f3ab9/default/replicationcontroller/web/spec.template).spec.containers", + "e8f3ab9/default/replicationcontroller/web?spec.template).spec.containers", nil, "invalid Field", }, @@ -184,7 +184,79 @@ func TestParseBadSRLs(t *testing.T) { if err == nil { t.Errorf("%s(%d) did not return error (expected error prefix: %s)", test.in, i, test.outStr) } else if !strings.HasPrefix(err.Error(), test.outStr) { - t.Errorf("%s(%d) wrong error (expected error prefix: %s)", test.in, i, test.outStr) + t.Errorf("%s(%d) wrong error: '%v' (expected error prefix: %s)", test.in, i, err.Error(), test.outStr) + } + } +} + +// PartTest checks if parts are being properly created for SRLs +// rawsrl is the input and the remaining fields are the output. Empty fields mean the related element was missing. +type PartTest struct { + rawsrl string + oid string + path string + field string +} + +func (t PartTest) String() string { + return fmt.Sprintf("rawsrl=%s, oid=%s, path=%s, field=%s", t.rawsrl, t.oid, t.path, t.field) +} + +var partTests = []PartTest{ + { + rawsrl: "oid", + oid: "oid", + }, + { + rawsrl: "oid/", + oid: "oid", + }, + { + rawsrl: "oid/?", + oid: "oid", + }, + { + rawsrl: "oid//////", + oid: "oid", + path: "/////", + }, + { + rawsrl: "oid//////?", + oid: "oid", + path: "/////", + }, + { + rawsrl: "oid//////?**", + oid: "oid", + path: "/////", + field: "**", + }, + { + rawsrl: "oid??//////?**", + oid: "oid??", + path: "/////", + field: "**", + }, + { + rawsrl: "oid//////?//", + oid: "oid", + path: "/////", + field: "//", + }, + { + rawsrl: "oid???", + oid: "oid???", + }, +} + +func TestParts(t *testing.T) { + for i, expected := range partTests { + input := expected.rawsrl + actual := PartTest{rawsrl: input} + actual.oid, actual.path, actual.field = parts(input) + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Part %d:\n\thave %v\n\twant %v\n", i, actual, expected) } } } From a18f221c3ee6c7a752c29c250109e043d416f7ea Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 28 Jun 2016 14:55:11 -0700 Subject: [PATCH 42/79] srl test fixes --- pkg/data/srl.go | 3 --- pkg/data/srl_test.go | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pkg/data/srl.go b/pkg/data/srl.go index c059263..1bc6141 100644 --- a/pkg/data/srl.go +++ b/pkg/data/srl.go @@ -169,9 +169,6 @@ func checkIllegalParens(fieldStr string) error { inParen := false for _, c := range fieldStr { if c == '(' { - if inParen { - return errors.New("already opened parentheses") - } inParen = true } else if c == ')' { if !inParen { diff --git a/pkg/data/srl_test.go b/pkg/data/srl_test.go index a561f64..e46a620 100644 --- a/pkg/data/srl_test.go +++ b/pkg/data/srl_test.go @@ -30,6 +30,14 @@ var goodSRLs = []SRLTest{ }, "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", }, + // treeish only, with double slash + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9//", + &SRL{ + Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + }, + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", + }, // shortened treeish { "a434f0b", @@ -128,6 +136,13 @@ var badSRLs = []SRLTest{ nil, "git object ID was too short", }, + // long oid + // short OID + { + "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9d", + nil, + "git object ID was too long", + }, // invalid characters in ID { "a343invalidID", @@ -176,6 +191,12 @@ var badSRLs = []SRLTest{ nil, "invalid Field", }, + // already open parentheses + { + "e8f3ab9/default/replicationcontroller/web?spec.template.spec.co(ntainers(0)", + nil, + "invalid Field", + }, } func TestParseBadSRLs(t *testing.T) { From baa2ebc3109464a0adb09bebef8c8beb9cd61267 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 13:10:41 -0700 Subject: [PATCH 43/79] Renamed 'Spread Resource Locator' to 'Spread Resource Identifier' --- pkg/data/{srl.go => sri.go} | 40 ++++++------- pkg/data/{srl_test.go => sri_test.go} | 86 +++++++++++++-------------- 2 files changed, 63 insertions(+), 63 deletions(-) rename pkg/data/{srl.go => sri.go} (80%) rename pkg/data/{srl_test.go => sri_test.go} (83%) diff --git a/pkg/data/srl.go b/pkg/data/sri.go similarity index 80% rename from pkg/data/srl.go rename to pkg/data/sri.go index 1bc6141..53eb6aa 100644 --- a/pkg/data/srl.go +++ b/pkg/data/sri.go @@ -22,21 +22,21 @@ const ( FieldDelimiter = "?" ) -// A SRL represents a parsed Spread Resource Locator (SRL), a globally unique address for an object or field stored within a repository. +// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. // This is represented as: // -// treeish/path[?fieldpath] -type SRL struct { +// treeish/path[?field] +type SRI struct { // Treeish is a Git Object ID to either a Commit or a Tree Git object. // The use of a Git OID (treeish) allows for any object or field to be addressed regardless if it is accessible. // The Object ID may be truncated down to a minimum of 7 characters. Treeish string - // Path to the Spread Object being addressed. If omitted, SRL refers to treeish. + // Path to the Spread Object being addressed. If omitted, SRI refers to treeish. // This will be traversed starting from the given Treeish. Path string - // Field specifies a path to the field within the Object that is being referred to. If omitted, the SRL refers to the entire object. + // Field specifies a path to the field within the Object that is being referred to. If omitted, the SRI refers to the entire object. // Path must be given to have a Field. // Fieldpaths are specified by name using the character “.” to specify sub-fields. // Fieldpath of arrays are addressed using their 0 indexed position wrapped with parentheses. @@ -44,8 +44,8 @@ type SRL struct { Field string } -// String returns a textual representation of the SRL which will be similar to the input. -func (s *SRL) String() string { +// String returns a textual representation of the SRI which will be similar to the input. +func (s *SRI) String() string { str := s.Treeish if len(s.Path) > 0 { str += "/" + s.Path @@ -56,9 +56,9 @@ func (s *SRL) String() string { return str } -// ParseSRL parses rawsrl into SRL struct. -func ParseSRL(rawsrl string) (*SRL, error) { - oid, path, field := parts(rawsrl) +// ParseSRI parses rawsri into SRI struct. +func ParseSRI(rawsri string) (*SRI, error) { + oid, path, field := parts(rawsri) var err error if oid, err = ParseOID(oid); err != nil { return nil, err @@ -72,37 +72,37 @@ func ParseSRL(rawsrl string) (*SRL, error) { return nil, err } - return &SRL{ + return &SRI{ Treeish: oid, Path: path, Field: field, }, nil } -func parts(rawsrl string) (oid, path, field string) { - if len(rawsrl) == 0 { +func parts(rawsri string) (oid, path, field string) { + if len(rawsri) == 0 { return } // OID - pathDelim := strings.Index(rawsrl, PathDelimiter) + pathDelim := strings.Index(rawsri, PathDelimiter) // check if only OID if pathDelim == -1 { - oid = rawsrl + oid = rawsri return } - oid = rawsrl[:pathDelim] + oid = rawsri[:pathDelim] // Path - fieldDelim := strings.LastIndex(rawsrl, FieldDelimiter) + fieldDelim := strings.LastIndex(rawsri, FieldDelimiter) if fieldDelim == -1 { - if len(rawsrl) > pathDelim+1 { - path = rawsrl[pathDelim+1:] + if len(rawsri) > pathDelim+1 { + path = rawsri[pathDelim+1:] } return } - path, field = rawsrl[pathDelim+1:fieldDelim], rawsrl[fieldDelim+1:] + path, field = rawsri[pathDelim+1:fieldDelim], rawsri[fieldDelim+1:] return } diff --git a/pkg/data/srl_test.go b/pkg/data/sri_test.go similarity index 83% rename from pkg/data/srl_test.go rename to pkg/data/sri_test.go index e46a620..09ad28a 100644 --- a/pkg/data/srl_test.go +++ b/pkg/data/sri_test.go @@ -7,17 +7,17 @@ import ( "testing" ) -type SRLTest struct { +type SRITest struct { in string - out *SRL // nil if error + out *SRI // nil if error outStr string // expected string for success, prefix of error for failure } -var goodSRLs = []SRLTest{ +var goodSRIs = []SRITest{ // treeish only { "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", - &SRL{ + &SRI{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", }, "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", @@ -25,7 +25,7 @@ var goodSRLs = []SRLTest{ // treeish only, with slash { "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/", - &SRL{ + &SRI{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", }, "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", @@ -33,7 +33,7 @@ var goodSRLs = []SRLTest{ // treeish only, with double slash { "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9//", - &SRL{ + &SRI{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", }, "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", @@ -41,7 +41,7 @@ var goodSRLs = []SRLTest{ // shortened treeish { "a434f0b", - &SRL{ + &SRI{ Treeish: "a434f0b", }, "a434f0b", @@ -49,25 +49,25 @@ var goodSRLs = []SRLTest{ // treeish & path { "e8f3ab9/default/replicationcontroller/web/", - &SRL{ + &SRI{ Treeish: "e8f3ab9", Path: "default/replicationcontroller/web", }, "e8f3ab9/default/replicationcontroller/web", }, - // full SRL (no Field) + // full SRI (no Field) { "e8f3ab9/default/replicationcontroller/web/?", - &SRL{ + &SRI{ Treeish: "e8f3ab9", Path: "default/replicationcontroller/web", }, "e8f3ab9/default/replicationcontroller/web", }, - // full SRL + // full SRI { "e8f3ab9/default/replicationcontroller/web?spec.template.spec.containers(0)", - &SRL{ + &SRI{ Treeish: "e8f3ab9", Path: "default/replicationcontroller/web", Field: "spec.template.spec.containers(0)", @@ -76,7 +76,7 @@ var goodSRLs = []SRLTest{ }, { "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web/?spec.template.spec.containers(0)", - &SRL{ + &SRI{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", Path: "default/replicationcontroller/web", Field: "spec.template.spec.containers(0)", @@ -85,7 +85,7 @@ var goodSRLs = []SRLTest{ }, { "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9//default//replicationcontroller//web//?spec.template.spec.containers(0)", - &SRL{ + &SRI{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", Path: "default/replicationcontroller/web", Field: "spec.template.spec.containers(0)", @@ -94,7 +94,7 @@ var goodSRLs = []SRLTest{ }, { "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web/?spec.template.spec.containers(0)(1)", - &SRL{ + &SRI{ Treeish: "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9", Path: "default/replicationcontroller/web", Field: "spec.template.spec.containers(0)(1)", @@ -103,27 +103,27 @@ var goodSRLs = []SRLTest{ }, } -func sfmt(s *SRL) string { +func sfmt(s *SRI) string { if s == nil { - s = new(SRL) + s = new(SRI) } return fmt.Sprintf("treeish=%s, path=%s, field=%s", s.Treeish, s.Path, s.Field) } -func TestParseGoodSRLs(t *testing.T) { - for i, test := range goodSRLs { - srl, err := ParseSRL(test.in) +func TestParseGoodSRIs(t *testing.T) { + for i, test := range goodSRIs { + sri, err := ParseSRI(test.in) if err != nil { t.Errorf("%s(%d) failed with: %v", test.in, i, err) - } else if !reflect.DeepEqual(srl, test.out) { - t.Errorf("%s(%d):\n\thave %v\n\twant %v\n", test.in, i, sfmt(srl), sfmt(test.out)) - } else if srl.String() != test.outStr { - t.Errorf("%s(%d) - Bad Serialization:\n\thave %s\n\twant %s\n", test.in, i, srl.String(), test.outStr) + } else if !reflect.DeepEqual(sri, test.out) { + t.Errorf("%s(%d):\n\thave %v\n\twant %v\n", test.in, i, sfmt(sri), sfmt(test.out)) + } else if sri.String() != test.outStr { + t.Errorf("%s(%d) - Bad Serialization:\n\thave %s\n\twant %s\n", test.in, i, sri.String(), test.outStr) } } } -var badSRLs = []SRLTest{ +var badSRIs = []SRITest{ // empty string { "", @@ -199,9 +199,9 @@ var badSRLs = []SRLTest{ }, } -func TestParseBadSRLs(t *testing.T) { - for i, test := range badSRLs { - _, err := ParseSRL(test.in) +func TestParseBadSRIs(t *testing.T) { + for i, test := range badSRIs { + _, err := ParseSRI(test.in) if err == nil { t.Errorf("%s(%d) did not return error (expected error prefix: %s)", test.in, i, test.outStr) } else if !strings.HasPrefix(err.Error(), test.outStr) { @@ -210,70 +210,70 @@ func TestParseBadSRLs(t *testing.T) { } } -// PartTest checks if parts are being properly created for SRLs -// rawsrl is the input and the remaining fields are the output. Empty fields mean the related element was missing. +// PartTest checks if parts are being properly created for SRIs +// rawsri is the input and the remaining fields are the output. Empty fields mean the related element was missing. type PartTest struct { - rawsrl string + rawsri string oid string path string field string } func (t PartTest) String() string { - return fmt.Sprintf("rawsrl=%s, oid=%s, path=%s, field=%s", t.rawsrl, t.oid, t.path, t.field) + return fmt.Sprintf("rawsri=%s, oid=%s, path=%s, field=%s", t.rawsri, t.oid, t.path, t.field) } var partTests = []PartTest{ { - rawsrl: "oid", + rawsri: "oid", oid: "oid", }, { - rawsrl: "oid/", + rawsri: "oid/", oid: "oid", }, { - rawsrl: "oid/?", + rawsri: "oid/?", oid: "oid", }, { - rawsrl: "oid//////", + rawsri: "oid//////", oid: "oid", path: "/////", }, { - rawsrl: "oid//////?", + rawsri: "oid//////?", oid: "oid", path: "/////", }, { - rawsrl: "oid//////?**", + rawsri: "oid//////?**", oid: "oid", path: "/////", field: "**", }, { - rawsrl: "oid??//////?**", + rawsri: "oid??//////?**", oid: "oid??", path: "/////", field: "**", }, { - rawsrl: "oid//////?//", + rawsri: "oid//////?//", oid: "oid", path: "/////", field: "//", }, { - rawsrl: "oid???", + rawsri: "oid???", oid: "oid???", }, } func TestParts(t *testing.T) { for i, expected := range partTests { - input := expected.rawsrl - actual := PartTest{rawsrl: input} + input := expected.rawsri + actual := PartTest{rawsri: input} actual.oid, actual.path, actual.field = parts(input) if !reflect.DeepEqual(actual, expected) { From 6781e7522cf14495abf9793d5dd4abe52fc81bfc Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 13:29:21 -0700 Subject: [PATCH 44/79] compile SRI regex checks during init --- pkg/data/sri.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/data/sri.go b/pkg/data/sri.go index 53eb6aa..5a2b246 100644 --- a/pkg/data/sri.go +++ b/pkg/data/sri.go @@ -14,14 +14,16 @@ const ( MinObjectIDLen = 7 ValidOIDChars = "abcdef0123456789" - OIDRegex = `[^a-f0-9]+` - PathRegex = `[^a-zA-Z0-9./]+` - FieldRegex = `[^a-zA-Z0-9./()]+` - PathDelimiter = "/" FieldDelimiter = "?" ) +var ( + OIDRegex = regexp.MustCompile(`[^a-f0-9]+`) + PathRegex = regexp.MustCompile(`[^a-zA-Z0-9./]+`) + FieldRegex = regexp.MustCompile(`[^a-zA-Z0-9./()]+`) +) + // A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. // This is represented as: // @@ -114,7 +116,7 @@ func ParseOID(oidStr string) (string, error) { } // check has valid chars - if regexp.MustCompile(OIDRegex).MatchString(oidStr) { + if OIDRegex.MatchString(oidStr) { return "", fmt.Errorf("invalid Treeish, invalid character in '%s' (only can contain '%s')", oidStr, ValidOIDChars) } return oidStr, nil @@ -126,7 +128,7 @@ func ParsePath(pathStr string) (string, error) { } // check has valid chars - if regexp.MustCompile(PathRegex).MatchString(pathStr) { + if PathRegex.MatchString(pathStr) { return "", fmt.Errorf("invalid Path, invalid character in '%s' (must match regex '%s')", pathStr, PathRegex) } @@ -154,7 +156,7 @@ func ParseField(fieldStr string) (string, error) { } // check has valid chars - if regexp.MustCompile(FieldRegex).MatchString(fieldStr) { + if FieldRegex.MatchString(fieldStr) { return "", fmt.Errorf("invalid Field, invalid character in '%s' (must match regex '%s')", fieldStr, FieldRegex) } From b31b3c5e7c0c2ad54408c7ff40f4c692baab369f Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 14:14:46 -0700 Subject: [PATCH 45/79] abstracted creating objects in git datastore and allowed wildcard in SRIs --- pkg/data/sri.go | 5 ++++- pkg/data/sri_test.go | 10 ++++++++++ pkg/project/index.go | 18 ++++++------------ pkg/project/object.go | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/pkg/data/sri.go b/pkg/data/sri.go index 5a2b246..5de4fa1 100644 --- a/pkg/data/sri.go +++ b/pkg/data/sri.go @@ -32,6 +32,7 @@ type SRI struct { // Treeish is a Git Object ID to either a Commit or a Tree Git object. // The use of a Git OID (treeish) allows for any object or field to be addressed regardless if it is accessible. // The Object ID may be truncated down to a minimum of 7 characters. + // A single character “*” indicates a relative reference, this intentionally can’t be formed into a URL. Treeish string // Path to the Spread Object being addressed. If omitted, SRI refers to treeish. @@ -109,7 +110,9 @@ func parts(rawsri string) (oid, path, field string) { } func ParseOID(oidStr string) (string, error) { - if len(oidStr) < MinObjectIDLen { + if len(oidStr) == 1 && oidStr[0] == '*' { + return "*", nil + } else if len(oidStr) < MinObjectIDLen { return "", fmt.Errorf("git object ID was too short (%d chars), must be at least %d chars.", len(oidStr), MinObjectIDLen) } else if len(oidStr) > MaxObjectIDLen { return "", fmt.Errorf("git object ID was too long (%d chars), must be %d chars at most.", len(oidStr), MaxObjectIDLen) diff --git a/pkg/data/sri_test.go b/pkg/data/sri_test.go index 09ad28a..c7fd14b 100644 --- a/pkg/data/sri_test.go +++ b/pkg/data/sri_test.go @@ -101,6 +101,16 @@ var goodSRIs = []SRITest{ }, "a434f0ba11e6ec04ca640f90b854dddcecd0c8d9/default/replicationcontroller/web?spec.template.spec.containers(0)(1)", }, + // no treeish SRI + { + "*/default/replicationcontroller/web/?spec.template.spec.containers(1)", + &SRI{ + Treeish: "*", + Path: "default/replicationcontroller/web", + Field: "spec.template.spec.containers(1)", + }, + "*/default/replicationcontroller/web?spec.template.spec.containers(1)", + }, } func sfmt(s *SRI) string { diff --git a/pkg/project/index.go b/pkg/project/index.go index 9127fb1..c01a5c2 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/golang/protobuf/proto" git "gopkg.in/libgit2/git2go.v23" "rsprd.com/spread/pkg/deploy" @@ -13,24 +12,19 @@ import ( ) func (p *Project) AddObjectToIndex(obj *pb.Object) error { - info := obj.GetInfo() - if info == nil { - return ErrNilObjectInfo - } - - data, err := proto.Marshal(obj) + oid, size, err := p.createObject(obj) if err != nil { - return fmt.Errorf("could not encode object: %v", err) + return fmt.Errorf("object couldn't be created: %v", err) } - oid, err := p.repo.CreateBlobFromBuffer(data) - if err != nil { - return fmt.Errorf("could not write Object as blob in Git repo: %v", err) + info := obj.GetInfo() + if info == nil { + return ErrNilObjectInfo } entry := &git.IndexEntry{ Mode: git.FilemodeBlob, - Size: uint32(len(data)), + Size: uint32(size), Id: oid, Path: info.Path, } diff --git a/pkg/project/object.go b/pkg/project/object.go index 58d01bc..49dd33a 100644 --- a/pkg/project/object.go +++ b/pkg/project/object.go @@ -24,6 +24,22 @@ func (p *Project) getObject(oid *git.Oid) (*pb.Object, error) { return obj, nil } +func (p *Project) createObject(obj *pb.Object) (oid *git.Oid, size int, err error) { + data, err := proto.Marshal(obj) + if err != nil { + err = fmt.Errorf("could not encode object: %v", err) + return + } + size = len(data) + + oid, err = p.repo.CreateBlobFromBuffer(data) + if err != nil { + err = fmt.Errorf("could not write Object as blob in Git repo: %v", err) + return + } + return +} + func (p *Project) getKubeObject(oid *git.Oid, path string) (deploy.KubeObject, error) { obj, err := p.getObject(oid) if err != nil { From fa6f250d5ff70d178a80faeb084d7b3d58c5445d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 14:44:08 -0700 Subject: [PATCH 46/79] added SRI and changed Link protobuf; changed SRI to use protobuf type --- pkg/spreadproto/object.pb.go | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index 2dfb87d..f8d23d4 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -338,7 +338,7 @@ func (m *Link) GetTarget() *SRI { type Object struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Info *ObjectInfo `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` - Fields *Array `protobuf:"bytes,3,opt,name=fields" json:"fields,omitempty"` + Fields []*Field `protobuf:"bytes,3,rep,name=fields" json:"fields,omitempty"` } func (m *Object) Reset() { *m = Object{} } @@ -353,7 +353,7 @@ func (m *Object) GetInfo() *ObjectInfo { return nil } -func (m *Object) GetFields() *Array { +func (m *Object) GetFields() []*Field { if m != nil { return m.Fields } @@ -382,28 +382,28 @@ func init() { var fileDescriptor0 = []byte{ // 381 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x5c, 0x52, 0x4d, 0x6b, 0xe2, 0x50, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x52, 0x4d, 0x6b, 0xe2, 0x50, 0x14, 0x35, 0xe6, 0x4b, 0x6f, 0x94, 0x71, 0xde, 0x30, 0x10, 0x1c, 0x67, 0x90, 0x0c, 0x03, 0xb3, 0xca, 0xa2, 0x6e, 0x8a, 0xbb, 0x0a, 0x2d, 0x5a, 0xfa, 0x01, 0xed, 0xa2, 0xd0, 0xdd, 0x8b, 0x5e, - 0x35, 0x35, 0x26, 0xe1, 0xe5, 0x29, 0xf8, 0x9b, 0xfa, 0x27, 0x7b, 0xdf, 0x4b, 0x62, 0xb5, 0x2b, - 0xb9, 0xf7, 0x1c, 0xcf, 0x39, 0xf7, 0xbc, 0x40, 0x27, 0x8b, 0xde, 0x70, 0x2e, 0xc3, 0x5c, 0x64, - 0x32, 0x63, 0x4e, 0x91, 0x0b, 0xe4, 0x8b, 0xe0, 0xdd, 0x00, 0xfb, 0x26, 0xc6, 0x64, 0xc1, 0x3c, - 0x30, 0x37, 0x78, 0xf0, 0x8d, 0xa1, 0xf1, 0xbf, 0xcd, 0x7a, 0xe0, 0xa4, 0xbb, 0x6d, 0x84, 0xc2, - 0x6f, 0xd2, 0x6c, 0x4c, 0x1b, 0xac, 0x0b, 0x66, 0x21, 0x85, 0x6f, 0x2a, 0x98, 0xc6, 0xef, 0xe0, - 0x46, 0x59, 0x96, 0x20, 0x4f, 0x7d, 0x8b, 0x56, 0x2d, 0x5a, 0xf5, 0xc1, 0x24, 0x0b, 0xdf, 0xa6, - 0xd1, 0xbb, 0xf0, 0xc2, 0xd2, 0x20, 0xbc, 0xe7, 0x39, 0x61, 0x7f, 0xc0, 0xe6, 0x42, 0xf0, 0x83, - 0xef, 0x68, 0xb4, 0x5b, 0xa3, 0x57, 0x6a, 0x49, 0xf8, 0x00, 0xac, 0x24, 0x4e, 0x37, 0xbe, 0xab, - 0xe1, 0x4e, 0x0d, 0xdf, 0xd1, 0x6e, 0xda, 0x98, 0xb8, 0x60, 0xef, 0x79, 0xb2, 0xc3, 0x60, 0x09, - 0x26, 0xe9, 0xb1, 0xbf, 0x60, 0xc5, 0x12, 0xb7, 0x94, 0xd5, 0x24, 0xf6, 0xcf, 0x13, 0xab, 0x70, - 0x46, 0xfb, 0xeb, 0x54, 0x8a, 0x43, 0x7f, 0x0c, 0xed, 0xe3, 0x70, 0x7e, 0xdc, 0xa0, 0x92, 0xd3, - 0xb7, 0x9d, 0x84, 0xd1, 0x3d, 0x8c, 0x9b, 0x97, 0x46, 0xf0, 0x0f, 0x6c, 0x9d, 0x4c, 0x51, 0x95, - 0x53, 0x51, 0x59, 0x9d, 0x53, 0x83, 0x11, 0x98, 0xcf, 0x4f, 0x33, 0xf6, 0x0d, 0x5c, 0x29, 0x10, - 0xe3, 0x62, 0x5d, 0x19, 0x74, 0xc0, 0xca, 0xb9, 0x5c, 0x6b, 0xfd, 0x36, 0x35, 0x67, 0x2f, 0x15, - 0xbd, 0xec, 0x2e, 0xb8, 0x05, 0x4b, 0x9d, 0xc5, 0x7e, 0x80, 0x97, 0xf3, 0xf9, 0x86, 0xaf, 0xf0, - 0x81, 0x6f, 0xb1, 0xfa, 0xe7, 0x2f, 0x70, 0x24, 0x17, 0x2b, 0x94, 0x55, 0xb6, 0x63, 0x8d, 0xca, - 0xa7, 0x07, 0xad, 0x6c, 0x8f, 0x42, 0xc4, 0x0b, 0xd4, 0x5a, 0xad, 0xe0, 0x05, 0x9c, 0x47, 0xfd, - 0xaa, 0xca, 0x32, 0xfd, 0x94, 0x19, 0x52, 0x41, 0xe9, 0x32, 0xab, 0x44, 0x58, 0x2d, 0x52, 0x72, - 0x67, 0x84, 0xb0, 0xdf, 0xe0, 0xe8, 0x50, 0x85, 0x56, 0xfa, 0xfa, 0x22, 0x41, 0x1f, 0xe0, 0x84, - 0x5c, 0xdf, 0xa3, 0xc5, 0x27, 0xdd, 0x57, 0xaf, 0xe4, 0xea, 0x2f, 0x29, 0x72, 0xf4, 0xcf, 0xe8, - 0x23, 0x00, 0x00, 0xff, 0xff, 0x1c, 0x4d, 0xd5, 0x03, 0x60, 0x02, 0x00, 0x00, + 0x35, 0x35, 0x26, 0xe1, 0xe5, 0x29, 0xf8, 0x9b, 0xfa, 0x27, 0x7b, 0xdf, 0x4b, 0x62, 0x15, 0xba, + 0x0a, 0xf7, 0x9e, 0x93, 0x73, 0xee, 0x39, 0x09, 0x74, 0xb2, 0xe8, 0x0d, 0xe7, 0x32, 0xcc, 0x45, + 0x26, 0x33, 0xe6, 0x14, 0xb9, 0x40, 0xbe, 0x08, 0xde, 0x0d, 0xb0, 0x6f, 0x62, 0x4c, 0x16, 0xcc, + 0x03, 0x73, 0x83, 0x07, 0xdf, 0x18, 0x1a, 0xff, 0xdb, 0xac, 0x07, 0x4e, 0xba, 0xdb, 0x46, 0x28, + 0xfc, 0x26, 0xcd, 0xc6, 0xb4, 0xc1, 0xba, 0x60, 0x16, 0x52, 0xf8, 0xa6, 0x82, 0x69, 0xfc, 0x0e, + 0x6e, 0x94, 0x65, 0x09, 0xf2, 0xd4, 0xb7, 0x68, 0xd5, 0xa2, 0x55, 0x1f, 0x4c, 0xb2, 0xf0, 0x6d, + 0x1a, 0xbd, 0x0b, 0x2f, 0x2c, 0x0d, 0xc2, 0x7b, 0x9e, 0x13, 0xf6, 0x07, 0x6c, 0x2e, 0x04, 0x3f, + 0xf8, 0x8e, 0x46, 0xbb, 0x35, 0x7a, 0xa5, 0x96, 0x84, 0x0f, 0xc0, 0x4a, 0xe2, 0x74, 0xe3, 0xbb, + 0x1a, 0xee, 0xd4, 0xf0, 0x1d, 0xed, 0xa6, 0x8d, 0x89, 0x0b, 0xf6, 0x9e, 0x27, 0x3b, 0x0c, 0x96, + 0x60, 0x92, 0x1e, 0xfb, 0x0b, 0x56, 0x2c, 0x71, 0x4b, 0xb7, 0x9a, 0xc4, 0xfe, 0x79, 0x62, 0x15, + 0xce, 0x68, 0x7f, 0x9d, 0x4a, 0x71, 0xe8, 0x8f, 0xa1, 0x7d, 0x1c, 0xce, 0xc3, 0x0d, 0x2a, 0x39, + 0x9d, 0xed, 0xe4, 0x18, 0xdd, 0xc3, 0xb8, 0x79, 0x69, 0x04, 0xff, 0xc0, 0xd6, 0x97, 0x29, 0xaa, + 0x72, 0x2a, 0x2a, 0xab, 0x73, 0x6a, 0x30, 0x02, 0xf3, 0xf9, 0x69, 0xc6, 0xbe, 0x81, 0x2b, 0x05, + 0x62, 0x5c, 0xac, 0x2b, 0x83, 0x0e, 0x58, 0x39, 0x97, 0x6b, 0xad, 0xdf, 0xa6, 0xe6, 0xec, 0xa5, + 0xa2, 0x97, 0xdd, 0x05, 0xb7, 0x60, 0xa9, 0x58, 0xec, 0x07, 0x78, 0x39, 0x9f, 0x6f, 0xf8, 0x0a, + 0x1f, 0xf8, 0x16, 0xab, 0x37, 0x7f, 0x81, 0x23, 0xb9, 0x58, 0xa1, 0xac, 0x6e, 0x3b, 0xd6, 0xa8, + 0x7c, 0x7a, 0xd0, 0xca, 0xf6, 0x28, 0x44, 0xbc, 0x40, 0xad, 0xd5, 0x0a, 0x5e, 0xc0, 0x79, 0xd4, + 0x5f, 0x55, 0x59, 0xa6, 0x9f, 0x32, 0x43, 0x2a, 0x28, 0x5d, 0x66, 0x95, 0x08, 0xab, 0x45, 0x4a, + 0xee, 0x8c, 0x10, 0xf6, 0x1b, 0x1c, 0x7d, 0x54, 0x41, 0x4a, 0x5f, 0x24, 0xeb, 0x03, 0x9c, 0x90, + 0xeb, 0x3c, 0x5a, 0x7c, 0xd2, 0x7d, 0xf5, 0x4a, 0xae, 0xfe, 0x93, 0x22, 0x47, 0x3f, 0x46, 0x1f, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x59, 0xbf, 0x3a, 0x05, 0x60, 0x02, 0x00, 0x00, } From 010c36342f53019b4bd760d05d90aaaccbbb934e Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 16:44:12 -0700 Subject: [PATCH 47/79] added tokenization of fieldpaths --- pkg/data/fields.go | 59 +++++++++++++++++++++++++++++++++ pkg/data/fields_test.go | 73 +++++++++++++++++++++++++++++++++++++++++ pkg/data/object.go | 10 ++++++ pkg/data/sri.go | 47 ++++++++++++++++++-------- 4 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 pkg/data/fields.go create mode 100644 pkg/data/fields_test.go diff --git a/pkg/data/fields.go b/pkg/data/fields.go new file mode 100644 index 0000000..b0a707b --- /dev/null +++ b/pkg/data/fields.go @@ -0,0 +1,59 @@ +package data + +import ( + pb "rsprd.com/spread/pkg/spreadproto" +) + +// Fields provides helper methods for working with protobuf object fields. +type Fields []*pb.Field + +// ResolveFields returns a field based on the provided field path in the format used for SRIs. +// An error is returned if the given path doesn't exist. +func (f Fields) ResolveField(fieldpath string) (*pb.Field, error) { + return nil, nil +} + +// Get returns the sub-fields of a field by name through an O(n) operation. Nil is returned if no field exists. +func (f Fields) GetFields(name string) Fields { + for _, field := range f { + if field.Key == name { + return field.GetFields() + } + } + return nil +} + +// nextField returns the first field in a fieldpath and returns the remainder after removing the root element. +// It will return array as true if the field is an array. If there is no next field, an empty string in field will be returned. +func nextField(fieldpath string) (field, next string, array bool) { + fieldpath, err := ValidateField(fieldpath) + if err != nil { + return + } + + for i, c := range fieldpath { + // check for end of field chars + if (c == '.' || c == '(') && i > 0 { + field = fieldpath[:i] + if c == '.' { + i++ + } + + if len(fieldpath) > i+1 { + next = fieldpath[i:] + } + return + } else if c == ')' { + field = fieldpath[1:i] + if len(fieldpath) > i+2 { + next = fieldpath[i+1:] + } + array = true + return + } else if i+1 == len(fieldpath) { + field = fieldpath + return + } + } + return +} diff --git a/pkg/data/fields_test.go b/pkg/data/fields_test.go new file mode 100644 index 0000000..fb33cbf --- /dev/null +++ b/pkg/data/fields_test.go @@ -0,0 +1,73 @@ +package data + +import ( + "testing" +) + +type NextFieldTest struct { + FieldStr string + NextFields []NextField +} + +type NextField struct { + name string + array bool +} + +var nextFieldTests = []NextFieldTest{ + { + "spec.template.spec.containers(0)", + []NextField{ + {name: "spec"}, {name: "template"}, {name: "spec"}, {name: "containers"}, + {name: "0", array: true}, + }, + }, + { + "(0)(1)(2)(3)(4)(5)ape", + []NextField{ + {name: "0", array: true}, {name: "1", array: true}, {name: "2", array: true}, + {name: "3", array: true}, {name: "4", array: true}, {name: "5", array: true}, + {name: "ape"}, + }, + }, + { + "spec(0)helo.eat.cheese", + []NextField{ + {name: "spec"}, {name: "0", array: true}, {name: "helo"}, {name: "eat"}, {name: "cheese"}, + }, + }, + // invalid (two dots + dot at beginning) + { + FieldStr: "..spec.template.spec.containers(0)", + }, + // invalid (no index specified for array) + { + FieldStr: "spec.template.spec.containers()", + }, +} + +func TestNextField(t *testing.T) { + for i, test := range nextFieldTests { + field, next, array := "", test.FieldStr, false + for fNum, curField := range test.NextFields { + field, next, array = nextField(next) + + if len(test.NextFields) == 0 && (len(field)+len(next) != 0 || array) { + t.Errorf("test %d: should have returned nothing", i) + } + + if field != curField.name { + t.Errorf("test %d: field for step %d does not match. expected: %s, actual: %s", i, fNum, + curField.name, field) + } + + if array != curField.array { + expected := "field" + if curField.array { + expected = "array" + } + t.Errorf("test %d: expecting %s for step %d", i, expected, fNum) + } + } + } +} diff --git a/pkg/data/object.go b/pkg/data/object.go index 076af19..0f08648 100644 --- a/pkg/data/object.go +++ b/pkg/data/object.go @@ -25,6 +25,16 @@ func CreateObject(name, path string, ptr interface{}) (*pb.Object, error) { return ObjectFromMap(name, path, jsonData) } +// GetFieldFromObject returns a pointer to field in obj based on an SRI. SRI must point to Field. +func GetFieldFromObject(obj *pb.Object, field *SRI) (*pb.Field, error) { + if !field.IsField() { + return nil, errors.New("passed SRI is not a field") + } + + fields := Fields(obj.GetFields()) + return fields.ResolveField(field.Path) +} + // ObjectFromMap creates an Object, using the entries of a map as fields. // This supports maps embedded as values. It is assumed that types are limited to JSON types. func ObjectFromMap(name, path string, data map[string]interface{}) (*pb.Object, error) { diff --git a/pkg/data/sri.go b/pkg/data/sri.go index 5de4fa1..a4a422e 100644 --- a/pkg/data/sri.go +++ b/pkg/data/sri.go @@ -59,19 +59,34 @@ func (s *SRI) String() string { return str } +// IsTreeish is true if identifier points to tree. +func (s *SRI) IsTree() bool { + return !s.IsObject() && !s.IsField() +} + +// IsObject is true if points to object. +func (s *SRI) IsObject() bool { + return len(s.Field) == 0 && len(s.Path) > 0 +} + +// IsField is true if points to field. +func (s *SRI) IsField() bool { + return len(s.Field) > 0 && len(s.Path) > 0 +} + // ParseSRI parses rawsri into SRI struct. func ParseSRI(rawsri string) (*SRI, error) { oid, path, field := parts(rawsri) var err error - if oid, err = ParseOID(oid); err != nil { + if oid, err = ValidateOID(oid); err != nil { return nil, err } - if path, err = ParsePath(path); err != nil { + if path, err = ValidatePath(path); err != nil { return nil, err } - if field, err = ParseField(field); err != nil { + if field, err = ValidateField(field); err != nil { return nil, err } @@ -109,7 +124,7 @@ func parts(rawsri string) (oid, path, field string) { return } -func ParseOID(oidStr string) (string, error) { +func ValidateOID(oidStr string) (string, error) { if len(oidStr) == 1 && oidStr[0] == '*' { return "*", nil } else if len(oidStr) < MinObjectIDLen { @@ -125,7 +140,7 @@ func ParseOID(oidStr string) (string, error) { return oidStr, nil } -func ParsePath(pathStr string) (string, error) { +func ValidatePath(pathStr string) (string, error) { if len(pathStr) == 0 { return "", nil } @@ -145,7 +160,7 @@ func ParsePath(pathStr string) (string, error) { return pathStr, nil } -func ParseField(fieldStr string) (string, error) { +func ValidateField(fieldStr string) (string, error) { if len(fieldStr) == 0 { return "", nil } @@ -154,6 +169,10 @@ func ParseField(fieldStr string) (string, error) { return "", errors.New("invalid Field: cannot begin with '.'") } + if fieldStr[len(fieldStr)-1] == '.' { + return "", errors.New("invalid Field: cannot end with '.'") + } + if strings.Contains(fieldStr, "..") { return "", errors.New("invalid Field: cannot repeat '.'") } @@ -171,21 +190,23 @@ func ParseField(fieldStr string) (string, error) { } func checkIllegalParens(fieldStr string) error { - inParen := false - for _, c := range fieldStr { + inParen := -1 + for i, c := range fieldStr { if c == '(' { - inParen = true + inParen = i } else if c == ')' { - if !inParen { + if inParen == -1 { return errors.New("closed parenthese when one hasn't been opened") + } else if inParen == i-1 { + return errors.New("must specify array position, cannot have '()'") } - inParen = false - } else if inParen && !unicode.IsNumber(c) { + inParen = -1 + } else if inParen != -1 && !unicode.IsNumber(c) { return errors.New("only numeric characters can be used in parentheses") } } - if inParen { + if inParen != -1 { return errors.New("unclosed parentheses") } From ae3faf173c7db41f5b9cd9b1eb00f7c316516e4a Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 17:58:41 -0700 Subject: [PATCH 48/79] adding links --- cli/link.go | 68 ++++++++++++++++++++++++++++++++++++++++ pkg/data/fields.go | 32 ++++++++++++++++--- pkg/data/link.go | 26 +++++++++++++++ pkg/data/object.go | 2 +- pkg/deploy/deployment.go | 9 ++++++ 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 cli/link.go create mode 100644 pkg/data/link.go diff --git a/cli/link.go b/cli/link.go new file mode 100644 index 0000000..1f0b1d5 --- /dev/null +++ b/cli/link.go @@ -0,0 +1,68 @@ +package cli + +import ( + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/data" + "rsprd.com/spread/pkg/deploy" +) + +// Link allows the links to be created on the Index +func (s SpreadCli) Link() *cli.Command { + return &cli.Command{ + Name: "link", + Usage: "spread link ", + Description: "Create/remove links on Index", + Action: func(c *cli.Context) { + targetUrl := c.Args().First() + if len(targetUrl) == 0 { + s.fatalf("A target URL must be specified") + } + + target, err := data.ParseSRI(targetUrl) + if err != nil { + s.fatalf("Error using target: %v", err) + } + + attachPoint := c.Args().Get(1) + if len(attachPoint) == 0 { + s.fatalf("An attach point must be specified") + } + + attach, err := data.ParseSRI(attachPoint) + if err != nil { + s.fatalf("Error using attach-point: %v", err) + } + + proj := s.projectOrDie() + dep, err := proj.Index() + if err != nil { + s.fatalf("Error retrieving index: %v", err) + } + + kubeObj, err := dep.Get(attach.Path) + if err != nil { + s.fatalf("Could not get object: %v", err) + } + + path, err := deploy.ObjectPath(kubeObj) + if err != nil { + s.fatalf("Failed to determine path to save object: %v", err) + } + + obj, err := data.CreateObject(kubeObj.GetObjectMeta().GetName(), path, kubeObj) + if err != nil { + s.fatalf("failed to encode object: %v", err) + } + + link := data.NewLink("test", target, false) + if err = data.CreateLinkInObject(obj, link, attach); err != nil { + s.fatalf("Could not create link: %v", err) + } + + if err = proj.AddObjectToIndex(obj); err != nil { + s.fatalf("Failed to add object to Git index: %v", err) + } + }, + } +} diff --git a/pkg/data/fields.go b/pkg/data/fields.go index b0a707b..78b7d63 100644 --- a/pkg/data/fields.go +++ b/pkg/data/fields.go @@ -1,6 +1,8 @@ package data import ( + "fmt" + pb "rsprd.com/spread/pkg/spreadproto" ) @@ -10,19 +12,41 @@ type Fields []*pb.Field // ResolveFields returns a field based on the provided field path in the format used for SRIs. // An error is returned if the given path doesn't exist. func (f Fields) ResolveField(fieldpath string) (*pb.Field, error) { - return nil, nil + field, next, _ := nextField(fieldpath) + if len(field) == 0 { + return nil, fmt.Errorf("could not resolve fieldpath '%s'", fieldpath) + } else if len(next) > 0 { + fields := f.GetFields(field) + if fields != nil { + return fields.ResolveField(next) + } + } else if out := f.Get(field); out != nil { + return out, nil + } + + return nil, fmt.Errorf("could not find field '%s'", field) } -// Get returns the sub-fields of a field by name through an O(n) operation. Nil is returned if no field exists. -func (f Fields) GetFields(name string) Fields { +// Get returns a field by name +func (f Fields) Get(name string) *pb.Field { for _, field := range f { if field.Key == name { - return field.GetFields() + return field } } return nil } +// GetFields returns the sub-fields of a field by name through an O(n) operation. Nil is returned if no field exists. +func (f Fields) GetFields(name string) Fields { + fields := f.Get(name).Fields + if fields == nil { + return nil + } + + return Fields(fields) +} + // nextField returns the first field in a fieldpath and returns the remainder after removing the root element. // It will return array as true if the field is an array. If there is no next field, an empty string in field will be returned. func nextField(fieldpath string) (field, next string, array bool) { diff --git a/pkg/data/link.go b/pkg/data/link.go new file mode 100644 index 0000000..5d3c0d9 --- /dev/null +++ b/pkg/data/link.go @@ -0,0 +1,26 @@ +package data + +import ( + pb "rsprd.com/spread/pkg/spreadproto" +) + +// NewLink creates a new link from with the given details. +func NewLink(packageName string, target *SRI, override bool) *pb.Link { + pbTarget := pb.SRI(*target) + return &pb.Link{ + PackageName: packageName, + Target: &pbTarget, + Override: override, + } +} + +// CreateLinkInObject creates a link from source to target with object. +func CreateLinkInObject(obj *pb.Object, target *pb.Link, source *SRI) error { + field, err := GetFieldFromObject(obj, source) + if err != nil { + return err + } + + field.Link = target + return nil +} diff --git a/pkg/data/object.go b/pkg/data/object.go index 0f08648..8a6f8b8 100644 --- a/pkg/data/object.go +++ b/pkg/data/object.go @@ -32,7 +32,7 @@ func GetFieldFromObject(obj *pb.Object, field *SRI) (*pb.Field, error) { } fields := Fields(obj.GetFields()) - return fields.ResolveField(field.Path) + return fields.ResolveField(field.Field) } // ObjectFromMap creates an Object, using the entries of a map as fields. diff --git a/pkg/deploy/deployment.go b/pkg/deploy/deployment.go index 48df3e0..eda858a 100644 --- a/pkg/deploy/deployment.go +++ b/pkg/deploy/deployment.go @@ -61,6 +61,15 @@ func (d *Deployment) AddDeployment(deployment Deployment) (err error) { return nil } +// Get returns the object with the given path from the Deployment. Error is returned if object does not exist. +func (d *Deployment) Get(name string) (KubeObject, error) { + obj, ok := d.objects[name] + if !ok { + return nil, fmt.Errorf("no object '%s' exists", name) + } + return obj, nil +} + // Equal performs a deep equality check between Deployments. Internal ordering is ignored. func (d *Deployment) Equal(other *Deployment) bool { if other == nil { From 6718bf2684e4426d1161085f1bac280d290c4739 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 29 Jun 2016 18:57:05 -0700 Subject: [PATCH 49/79] fixed nil pointer exception --- pkg/data/fields.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/data/fields.go b/pkg/data/fields.go index 78b7d63..6a4801a 100644 --- a/pkg/data/fields.go +++ b/pkg/data/fields.go @@ -39,12 +39,15 @@ func (f Fields) Get(name string) *pb.Field { // GetFields returns the sub-fields of a field by name through an O(n) operation. Nil is returned if no field exists. func (f Fields) GetFields(name string) Fields { - fields := f.Get(name).Fields - if fields == nil { + field := f.Get(name) + if field == nil { return nil } - return Fields(fields) + if field.Fields != nil { + return Fields(field.Fields) + } + return nil } // nextField returns the first field in a fieldpath and returns the remainder after removing the root element. From a4ba705d0ce773873d811de3e66636107da7b8b1 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 8 Jul 2016 12:43:01 -0700 Subject: [PATCH 50/79] updated links to use new proto schema --- cli/add.go | 6 +- cli/link.go | 6 +- pkg/data/decode.go | 9 +- pkg/data/document.go | 78 +++++++++++++ pkg/data/encode.go | 10 +- pkg/data/fields.go | 83 ++++++++------ pkg/data/fields_test.go | 27 +++-- pkg/data/link.go | 19 +++- pkg/data/object.go | 58 ++-------- pkg/data/sri.go | 25 +++-- pkg/deploy/object.go | 4 +- pkg/project/{object.go => document.go} | 22 ++-- pkg/project/index.go | 6 +- pkg/spreadproto/object.pb.go | 146 ++++++++++++------------- proto/object.proto | 22 ++-- 15 files changed, 298 insertions(+), 223 deletions(-) create mode 100644 pkg/data/document.go rename pkg/project/{object.go => document.go} (55%) diff --git a/cli/add.go b/cli/add.go index e2f96f1..e64e4fd 100644 --- a/cli/add.go +++ b/cli/add.go @@ -71,13 +71,13 @@ func (s SpreadCli) Add() *cli.Command { s.fatalf("Failed to determine path to save object: %v", err) } - obj, err := data.CreateObject(kubeObj.GetObjectMeta().GetName(), path, kubeObj) + obj, err := data.CreateDocument(kubeObj.GetObjectMeta().GetName(), path, kubeObj) if err != nil { - s.fatalf("failed to encode object: %v", err) + s.fatalf("failed to encode document: %v", err) } proj := s.projectOrDie() - err = proj.AddObjectToIndex(obj) + err = proj.AddDocumentToIndex(obj) if err != nil { s.fatalf("Failed to add object to Git index: %v", err) } diff --git a/cli/link.go b/cli/link.go index 1f0b1d5..7f429a0 100644 --- a/cli/link.go +++ b/cli/link.go @@ -50,17 +50,17 @@ func (s SpreadCli) Link() *cli.Command { s.fatalf("Failed to determine path to save object: %v", err) } - obj, err := data.CreateObject(kubeObj.GetObjectMeta().GetName(), path, kubeObj) + obj, err := data.CreateDocument(kubeObj.GetObjectMeta().GetName(), path, kubeObj) if err != nil { s.fatalf("failed to encode object: %v", err) } link := data.NewLink("test", target, false) - if err = data.CreateLinkInObject(obj, link, attach); err != nil { + if err = data.CreateLinkInDocument(obj, link, attach); err != nil { s.fatalf("Could not create link: %v", err) } - if err = proj.AddObjectToIndex(obj); err != nil { + if err = proj.AddDocumentToIndex(obj); err != nil { s.fatalf("Failed to add object to Git index: %v", err) } }, diff --git a/pkg/data/decode.go b/pkg/data/decode.go index 73a2422..20d486b 100644 --- a/pkg/data/decode.go +++ b/pkg/data/decode.go @@ -19,16 +19,19 @@ func decodeField(field *pb.Field) (interface{}, error) { return v.Str, nil case *pb.Field_Boolean: return v.Boolean, nil - case *pb.Field_Obj: - return decodeMap(v.Obj.Item) + case *pb.Field_Object: + return decodeObject(v.Object.GetItems()) case *pb.Field_Array: return decodeArray(v.Array.GetItems()) + case *pb.Field_Link: + // TODO: IMPLEMENT FOLLOWING LINKS + return nil, nil } return nil, fmt.Errorf("unknown type for Field '%s'", field.Key) } -func decodeMap(fields map[string]*pb.Field) (map[string]interface{}, error) { +func decodeObject(fields map[string]*pb.Field) (map[string]interface{}, error) { out := make(map[string]interface{}, len(fields)) for k, field := range fields { val, err := decodeField(field) diff --git a/pkg/data/document.go b/pkg/data/document.go new file mode 100644 index 0000000..5125ab0 --- /dev/null +++ b/pkg/data/document.go @@ -0,0 +1,78 @@ +package data + +import ( + "encoding/json" + "fmt" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +// GetFieldFromDocument returns a pointer to field in doc based on the given field path. +func GetFieldFromDocument(doc *pb.Document, fieldpath string) (*pb.Field, error) { + root := doc.GetRoot() + if root == nil { + return nil, fmt.Errorf("root for document '%s' was nil", doc.Name) + } + + field, err := ResolveRelativeField(root, fieldpath) + if err != nil { + return nil, fmt.Errorf("could not resolve '%s': %v", fieldpath, err) + } + return field, err +} + +// CreateDocument creates a Document with an Object as it's value using CreateObject. +func CreateDocument(name, path string, ptr interface{}) (*pb.Document, error) { + obj, err := CreateObject("", ptr) + if err != nil { + return nil, fmt.Errorf("could not create object for document: %v", err) + } + return &pb.Document{ + Name: name, + Info: &pb.DocumentInfo{ + Path: path, + }, + Root: &pb.Field{ + Value: &pb.Field_Object{ + Object: obj, + }, + }, + }, nil +} + +func Unmarshal(doc *pb.Document, ptr interface{}) error { + fieldMap, err := MapFromDocument(doc) + if err != nil { + return err + } + + // TODO: use reflection to replace this + jsonData, err := json.Marshal(&fieldMap) + if err != nil { + return fmt.Errorf("unable to generate JSON from document data: %v", err) + } + + return json.Unmarshal(jsonData, ptr) +} + +func MapFromDocument(doc *pb.Document) (map[string]interface{}, error) { + obj, err := getObjectFromDoc(doc) + if err != nil { + return nil, err + } + + return MapFromObject(obj) +} + +func getObjectFromDoc(doc *pb.Document) (*pb.Object, error) { + root := doc.GetRoot() + if root == nil { + return nil, fmt.Errorf("document '%s' does not have a root", doc.Name) + } + + obj := root.GetObject() + if obj == nil { + return nil, fmt.Errorf("root field of document '%s' does not have an object as it's value", doc.Name) + } + return obj, nil +} diff --git a/pkg/data/encode.go b/pkg/data/encode.go index 5d45b30..371e382 100644 --- a/pkg/data/encode.go +++ b/pkg/data/encode.go @@ -32,7 +32,7 @@ func buildField(key string, data interface{}) (*pb.Field, error) { case []interface{}: return buildArray(key, typedData) case map[string]interface{}: - return buildMap(key, typedData) + return buildObject(key, typedData) } if field.Value == nil { @@ -62,7 +62,7 @@ func buildArray(key string, data []interface{}) (*pb.Field, error) { }, nil } -func buildMap(key string, data map[string]interface{}) (*pb.Field, error) { +func buildObject(key string, data map[string]interface{}) (*pb.Field, error) { obj := make(map[string]*pb.Field, len(data)) for k, v := range data { field, err := buildField(k, v) @@ -74,9 +74,9 @@ func buildMap(key string, data map[string]interface{}) (*pb.Field, error) { return &pb.Field{ Key: key, - Value: &pb.Field_Obj{ - Obj: &pb.Map{ - Item: obj, + Value: &pb.Field_Object{ + Object: &pb.Object{ + Items: obj, }, }, }, nil diff --git a/pkg/data/fields.go b/pkg/data/fields.go index 6a4801a..6146f39 100644 --- a/pkg/data/fields.go +++ b/pkg/data/fields.go @@ -2,57 +2,65 @@ package data import ( "fmt" + "strconv" pb "rsprd.com/spread/pkg/spreadproto" ) -// Fields provides helper methods for working with protobuf object fields. -type Fields []*pb.Field - -// ResolveFields returns a field based on the provided field path in the format used for SRIs. -// An error is returned if the given path doesn't exist. -func (f Fields) ResolveField(fieldpath string) (*pb.Field, error) { - field, next, _ := nextField(fieldpath) - if len(field) == 0 { - return nil, fmt.Errorf("could not resolve fieldpath '%s'", fieldpath) - } else if len(next) > 0 { - fields := f.GetFields(field) - if fields != nil { - return fields.ResolveField(next) - } - } else if out := f.Get(field); out != nil { - return out, nil +func ResolveRelativeField(field *pb.Field, fieldpath string) (resolvedField *pb.Field, err error) { + fieldKey, arrIndex, next := nextField(fieldpath) + if arrIndex >= 0 { + resolvedField, err = getFromArrayField(field, arrIndex) + } else if len(fieldKey) > 0 { + resolvedField, err = getFromMapField(field, fieldKey) + } else { + err = fmt.Errorf("could not resolve fieldpath '%s'", fieldpath) } - return nil, fmt.Errorf("could not find field '%s'", field) + // return if err or no more fields to traverse (end of field path) + if err != nil || len(next) == 0 { + return + } + return ResolveRelativeField(resolvedField, next) } -// Get returns a field by name -func (f Fields) Get(name string) *pb.Field { - for _, field := range f { - if field.Key == name { - return field - } +func getFromArrayField(field *pb.Field, index int) (*pb.Field, error) { + fieldArr := field.GetArray() + if fieldArr == nil { + return nil, fmt.Errorf("field '%s' isn't an array, cannot access %s[%d]", field.Key, field.Key, index) } - return nil + + items := fieldArr.GetItems() + if items == nil { + return nil, fmt.Errorf("the array wrapper struct for the value of field '%s' had nil for items, cannot access %s[%d]", field.Key, field.Key, index) + } else if len(items)-1 < index { + return nil, fmt.Errorf("could not access %s[%d], the size of '%s' is %d", field.Key, index, field.Key, len(items)) + } + return items[index], nil } -// GetFields returns the sub-fields of a field by name through an O(n) operation. Nil is returned if no field exists. -func (f Fields) GetFields(name string) Fields { - field := f.Get(name) - if field == nil { - return nil +func getFromMapField(field *pb.Field, key string) (*pb.Field, error) { + fieldMap := field.GetObject() + if fieldMap == nil { + return nil, fmt.Errorf("field '%s' isn't an object, cannot access %s['%s']", field.Key, field.Key, key) + } + + items := fieldMap.GetItems() + if items == nil { + return nil, fmt.Errorf("the object wrapper struct for the value of field '%s' had nil for items, cannot access %s[%d]", field.Key, field.Key, key) } - if field.Fields != nil { - return Fields(field.Fields) + item, ok := items[key] + if !ok { + return nil, fmt.Errorf("no key '%s' in map for field '%s", key, field.Key) } - return nil + return item, nil } // nextField returns the first field in a fieldpath and returns the remainder after removing the root element. -// It will return array as true if the field is an array. If there is no next field, an empty string in field will be returned. -func nextField(fieldpath string) (field, next string, array bool) { +// It will return array as positive number or 0 if refers to array. If there is no next field, an empty string in field will be returned. +func nextField(fieldpath string) (field string, array int, next string) { + array = -1 fieldpath, err := ValidateField(fieldpath) if err != nil { return @@ -71,11 +79,14 @@ func nextField(fieldpath string) (field, next string, array bool) { } return } else if c == ')' { - field = fieldpath[1:i] + indexStr := fieldpath[1:i] if len(fieldpath) > i+2 { next = fieldpath[i+1:] } - array = true + + if num, err := strconv.ParseInt(indexStr, 10, 64); err == nil { + array = int(num) + } return } else if i+1 == len(fieldpath) { field = fieldpath diff --git a/pkg/data/fields_test.go b/pkg/data/fields_test.go index fb33cbf..19bc3a4 100644 --- a/pkg/data/fields_test.go +++ b/pkg/data/fields_test.go @@ -11,29 +11,28 @@ type NextFieldTest struct { type NextField struct { name string - array bool + array int } var nextFieldTests = []NextFieldTest{ { "spec.template.spec.containers(0)", []NextField{ - {name: "spec"}, {name: "template"}, {name: "spec"}, {name: "containers"}, - {name: "0", array: true}, + {name: "spec", array: -1}, {name: "template", array: -1}, {name: "spec", array: -1}, + {name: "containers", array: -1}, {array: 0}, }, }, { "(0)(1)(2)(3)(4)(5)ape", []NextField{ - {name: "0", array: true}, {name: "1", array: true}, {name: "2", array: true}, - {name: "3", array: true}, {name: "4", array: true}, {name: "5", array: true}, - {name: "ape"}, + {array: 0}, {array: 1}, {array: 2}, {array: 3}, {array: 4}, {array: 5}, {name: "ape", array: -1}, }, }, { "spec(0)helo.eat.cheese", []NextField{ - {name: "spec"}, {name: "0", array: true}, {name: "helo"}, {name: "eat"}, {name: "cheese"}, + {name: "spec", array: -1}, {array: 0}, {name: "helo", array: -1}, {name: "eat", array: -1}, + {name: "cheese", array: -1}, }, }, // invalid (two dots + dot at beginning) @@ -48,11 +47,11 @@ var nextFieldTests = []NextFieldTest{ func TestNextField(t *testing.T) { for i, test := range nextFieldTests { - field, next, array := "", test.FieldStr, false + field, array, next := "", -1, test.FieldStr for fNum, curField := range test.NextFields { - field, next, array = nextField(next) + field, array, next = nextField(next) - if len(test.NextFields) == 0 && (len(field)+len(next) != 0 || array) { + if len(test.NextFields) == 0 && (len(field)+len(next) != 0 || array < 0) { t.Errorf("test %d: should have returned nothing", i) } @@ -62,11 +61,11 @@ func TestNextField(t *testing.T) { } if array != curField.array { - expected := "field" - if curField.array { - expected = "array" + if curField.array != -1 { + t.Errorf("test %d: expecting array for step %d", i, fNum) + } else { + t.Errorf("test %d: expecting %d (got %d) for step %d", i, curField.array, array, fNum) } - t.Errorf("test %d: expecting %s for step %d", i, expected, fNum) } } } diff --git a/pkg/data/link.go b/pkg/data/link.go index 5d3c0d9..c8bba05 100644 --- a/pkg/data/link.go +++ b/pkg/data/link.go @@ -1,26 +1,33 @@ package data import ( + "errors" + pb "rsprd.com/spread/pkg/spreadproto" ) // NewLink creates a new link from with the given details. func NewLink(packageName string, target *SRI, override bool) *pb.Link { - pbTarget := pb.SRI(*target) return &pb.Link{ PackageName: packageName, - Target: &pbTarget, + Target: target.Proto(), Override: override, } } -// CreateLinkInObject creates a link from source to target with object. -func CreateLinkInObject(obj *pb.Object, target *pb.Link, source *SRI) error { - field, err := GetFieldFromObject(obj, source) +// CreateLinkInDocument creates a link from source to target with document. +func CreateLinkInDocument(doc *pb.Document, target *pb.Link, source *SRI) error { + if !source.IsField() { + return errors.New("passed SRI is not a field") + } + + field, err := GetFieldFromDocument(doc, source.Field) if err != nil { return err } - field.Link = target + field.Value = &pb.Field_Link{ + Link: target, + } return nil } diff --git a/pkg/data/object.go b/pkg/data/object.go index 8a6f8b8..23895e3 100644 --- a/pkg/data/object.go +++ b/pkg/data/object.go @@ -9,7 +9,7 @@ import ( ) // CreateObject uses reflection to convert the data (usually a struct) into an Object. -func CreateObject(name, path string, ptr interface{}) (*pb.Object, error) { +func CreateObject(key string, ptr interface{}) (*pb.Object, error) { data, err := json.Marshal(ptr) if err != nil { return nil, fmt.Errorf("unable to generate JSON object: %v", err) @@ -22,67 +22,33 @@ func CreateObject(name, path string, ptr interface{}) (*pb.Object, error) { return nil, err } - return ObjectFromMap(name, path, jsonData) -} - -// GetFieldFromObject returns a pointer to field in obj based on an SRI. SRI must point to Field. -func GetFieldFromObject(obj *pb.Object, field *SRI) (*pb.Field, error) { - if !field.IsField() { - return nil, errors.New("passed SRI is not a field") - } - - fields := Fields(obj.GetFields()) - return fields.ResolveField(field.Field) + return ObjectFromMap(key, jsonData) } // ObjectFromMap creates an Object, using the entries of a map as fields. // This supports maps embedded as values. It is assumed that types are limited to JSON types. -func ObjectFromMap(name, path string, data map[string]interface{}) (*pb.Object, error) { - obj := &pb.Object{ - Name: name, - Info: &pb.ObjectInfo{ - Path: path, - }, - } - - i := 0 - obj.Fields = &pb.Array{ - Items: make([]*pb.Field, len(data)), - } +func ObjectFromMap(key string, data map[string]interface{}) (*pb.Object, error) { + items := make(map[string]*pb.Field, len(data)) for k, v := range data { field, err := buildField(k, v) if err != nil { return nil, err } - obj.Fields.Items[i] = field - i++ - } - return obj, nil -} - -func Unmarshal(obj *pb.Object, ptr interface{}) error { - fieldMap, err := MapFromObject(obj) - if err != nil { - return err - } - - // TODO: use reflection to replace this - jsonData, err := json.Marshal(&fieldMap) - if err != nil { - return fmt.Errorf("unable to generate JSON from object data: %v", err) + items[k] = field } - - return json.Unmarshal(jsonData, ptr) + return &pb.Object{ + Items: items, + }, nil } func MapFromObject(obj *pb.Object) (map[string]interface{}, error) { - fields := obj.GetFields() - if fields == nil || fields.GetItems() == nil { + items := obj.GetItems() + if items == nil { return nil, ErrObjectNilFields } - out := make(map[string]interface{}, len(fields.GetItems())) - for _, field := range fields.GetItems() { + out := make(map[string]interface{}, len(items)) + for _, field := range items { val, err := decodeField(field) if err != nil { return nil, fmt.Errorf("could not decode field '%s': %v", field.Key, err) diff --git a/pkg/data/sri.go b/pkg/data/sri.go index a4a422e..288e488 100644 --- a/pkg/data/sri.go +++ b/pkg/data/sri.go @@ -7,6 +7,8 @@ import ( "regexp" "strings" "unicode" + + pb "rsprd.com/spread/pkg/spreadproto" ) const ( @@ -24,22 +26,22 @@ var ( FieldRegex = regexp.MustCompile(`[^a-zA-Z0-9./()]+`) ) -// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. +// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an document or field stored within a repository. // This is represented as: // // treeish/path[?field] type SRI struct { // Treeish is a Git Object ID to either a Commit or a Tree Git object. - // The use of a Git OID (treeish) allows for any object or field to be addressed regardless if it is accessible. + // The use of a Git OID (treeish) allows for any document or field to be addressed regardless if it is accessible. // The Object ID may be truncated down to a minimum of 7 characters. // A single character “*” indicates a relative reference, this intentionally can’t be formed into a URL. Treeish string - // Path to the Spread Object being addressed. If omitted, SRI refers to treeish. + // Path to the Spread Document being addressed. If omitted, SRI refers to treeish. // This will be traversed starting from the given Treeish. Path string - // Field specifies a path to the field within the Object that is being referred to. If omitted, the SRI refers to the entire object. + // Field specifies a path to the field within the Document that is being referred to. If omitted, the SRI refers to the entire document. // Path must be given to have a Field. // Fieldpaths are specified by name using the character “.” to specify sub-fields. // Fieldpath of arrays are addressed using their 0 indexed position wrapped with parentheses. @@ -59,13 +61,22 @@ func (s *SRI) String() string { return str } +// Proto returns the protobuf representation of an SRI +func (s *SRI) Proto() *pb.SRI { + return &pb.SRI{ + Treeish: s.Treeish, + Path: s.Path, + Field: s.Field, + } +} + // IsTreeish is true if identifier points to tree. func (s *SRI) IsTree() bool { - return !s.IsObject() && !s.IsField() + return !s.IsDocument() && !s.IsField() } -// IsObject is true if points to object. -func (s *SRI) IsObject() bool { +// IsDocument is true if points to document. +func (s *SRI) IsDocument() bool { return len(s.Field) == 0 && len(s.Path) > 0 } diff --git a/pkg/deploy/object.go b/pkg/deploy/object.go index c483c21..d9377f6 100644 --- a/pkg/deploy/object.go +++ b/pkg/deploy/object.go @@ -46,13 +46,13 @@ func objectKind(obj KubeObject) (types.GroupVersionKind, error) { return gkv, nil } -func KubeObjectFromObject(kind string, obj *pb.Object) (KubeObject, error) { +func KubeObjectFromDocument(kind string, doc *pb.Document) (KubeObject, error) { base := BaseObject(kind) if base == nil { return nil, fmt.Errorf("unable to find Kind for '%s'", kind) } - err := data.Unmarshal(obj, &base) + err := data.Unmarshal(doc, &base) if err != nil { return nil, err } diff --git a/pkg/project/object.go b/pkg/project/document.go similarity index 55% rename from pkg/project/object.go rename to pkg/project/document.go index 49dd33a..2729a6b 100644 --- a/pkg/project/object.go +++ b/pkg/project/document.go @@ -10,38 +10,38 @@ import ( pb "rsprd.com/spread/pkg/spreadproto" ) -func (p *Project) getObject(oid *git.Oid) (*pb.Object, error) { +func (p *Project) getDocument(oid *git.Oid) (*pb.Document, error) { blob, err := p.repo.LookupBlob(oid) if err != nil { - return nil, fmt.Errorf("failed to retrieve Object blob: %v", err) + return nil, fmt.Errorf("failed to retrieve Document blob: %v", err) } - obj := &pb.Object{} - err = proto.Unmarshal(blob.Contents(), obj) + doc := &pb.Document{} + err = proto.Unmarshal(blob.Contents(), doc) if err != nil { - return nil, fmt.Errorf("unable to unmarshal object protobuf: %v", err) + return nil, fmt.Errorf("unable to unmarshal document protobuf: %v", err) } - return obj, nil + return doc, nil } -func (p *Project) createObject(obj *pb.Object) (oid *git.Oid, size int, err error) { +func (p *Project) createDocument(obj *pb.Document) (oid *git.Oid, size int, err error) { data, err := proto.Marshal(obj) if err != nil { - err = fmt.Errorf("could not encode object: %v", err) + err = fmt.Errorf("could not encode document: %v", err) return } size = len(data) oid, err = p.repo.CreateBlobFromBuffer(data) if err != nil { - err = fmt.Errorf("could not write Object as blob in Git repo: %v", err) + err = fmt.Errorf("could not write Document as blob in Git repo: %v", err) return } return } func (p *Project) getKubeObject(oid *git.Oid, path string) (deploy.KubeObject, error) { - obj, err := p.getObject(oid) + doc, err := p.getDocument(oid) if err != nil { return nil, fmt.Errorf("failed to read object from Git repository: %v", err) } @@ -51,7 +51,7 @@ func (p *Project) getKubeObject(oid *git.Oid, path string) (deploy.KubeObject, e return nil, err } - kubeObj, err := deploy.KubeObjectFromObject(kind, obj) + kubeObj, err := deploy.KubeObjectFromDocument(kind, doc) if err != nil { return nil, err } diff --git a/pkg/project/index.go b/pkg/project/index.go index c01a5c2..52b8bf6 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -11,13 +11,13 @@ import ( pb "rsprd.com/spread/pkg/spreadproto" ) -func (p *Project) AddObjectToIndex(obj *pb.Object) error { - oid, size, err := p.createObject(obj) +func (p *Project) AddDocumentToIndex(doc *pb.Document) error { + oid, size, err := p.createDocument(doc) if err != nil { return fmt.Errorf("object couldn't be created: %v", err) } - info := obj.GetInfo() + info := doc.GetInfo() if info == nil { return ErrNilObjectInfo } diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index f8d23d4..44af730 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -10,12 +10,12 @@ It is generated from these files: It has these top-level messages: Field - Map + Object Array SRI Link - Object - ObjectInfo + Document + DocumentInfo */ package spreadproto @@ -39,7 +39,7 @@ type Field struct { // *Field_Number // *Field_Str // *Field_Boolean - // *Field_Obj + // *Field_Object // *Field_Array // *Field_Link Value isField_Value `protobuf_oneof:"value"` @@ -63,8 +63,8 @@ type Field_Str struct { type Field_Boolean struct { Boolean bool `protobuf:"varint,4,opt,name=boolean,oneof"` } -type Field_Obj struct { - Obj *Map `protobuf:"bytes,5,opt,name=obj,oneof"` +type Field_Object struct { + Object *Object `protobuf:"bytes,5,opt,name=object,oneof"` } type Field_Array struct { Array *Array `protobuf:"bytes,6,opt,name=array,oneof"` @@ -76,7 +76,7 @@ type Field_Link struct { func (*Field_Number) isField_Value() {} func (*Field_Str) isField_Value() {} func (*Field_Boolean) isField_Value() {} -func (*Field_Obj) isField_Value() {} +func (*Field_Object) isField_Value() {} func (*Field_Array) isField_Value() {} func (*Field_Link) isField_Value() {} @@ -108,9 +108,9 @@ func (m *Field) GetBoolean() bool { return false } -func (m *Field) GetObj() *Map { - if x, ok := m.GetValue().(*Field_Obj); ok { - return x.Obj +func (m *Field) GetObject() *Object { + if x, ok := m.GetValue().(*Field_Object); ok { + return x.Object } return nil } @@ -135,7 +135,7 @@ func (*Field) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, (*Field_Number)(nil), (*Field_Str)(nil), (*Field_Boolean)(nil), - (*Field_Obj)(nil), + (*Field_Object)(nil), (*Field_Array)(nil), (*Field_Link)(nil), } @@ -158,9 +158,9 @@ func _Field_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { } b.EncodeVarint(4<<3 | proto.WireVarint) b.EncodeVarint(t) - case *Field_Obj: + case *Field_Object: b.EncodeVarint(5<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Obj); err != nil { + if err := b.EncodeMessage(x.Object); err != nil { return err } case *Field_Array: @@ -204,13 +204,13 @@ func _Field_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) x, err := b.DecodeVarint() m.Value = &Field_Boolean{x != 0} return true, err - case 5: // value.obj + case 5: // value.object if wire != proto.WireBytes { return true, proto.ErrInternalBadWireType } - msg := new(Map) + msg := new(Object) err := b.DecodeMessage(msg) - m.Value = &Field_Obj{msg} + m.Value = &Field_Object{msg} return true, err case 6: // value.array if wire != proto.WireBytes { @@ -247,8 +247,8 @@ func _Field_OneofSizer(msg proto.Message) (n int) { case *Field_Boolean: n += proto.SizeVarint(4<<3 | proto.WireVarint) n += 1 - case *Field_Obj: - s := proto.Size(x.Obj) + case *Field_Object: + s := proto.Size(x.Object) n += proto.SizeVarint(5<<3 | proto.WireBytes) n += proto.SizeVarint(uint64(s)) n += s @@ -269,19 +269,19 @@ func _Field_OneofSizer(msg proto.Message) (n int) { return n } -// Map represents a map. -type Map struct { - Item map[string]*Field `protobuf:"bytes,1,rep,name=item" json:"item,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +// Object represents a map with strings for keys. +type Object struct { + Items map[string]*Field `protobuf:"bytes,1,rep,name=items" json:"items,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` } -func (m *Map) Reset() { *m = Map{} } -func (m *Map) String() string { return proto.CompactTextString(m) } -func (*Map) ProtoMessage() {} -func (*Map) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (m *Object) Reset() { *m = Object{} } +func (m *Object) String() string { return proto.CompactTextString(m) } +func (*Object) ProtoMessage() {} +func (*Object) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } -func (m *Map) GetItem() map[string]*Field { +func (m *Object) GetItems() map[string]*Field { if m != nil { - return m.Item + return m.Items } return nil } @@ -334,76 +334,76 @@ func (m *Link) GetTarget() *SRI { return nil } -// Object holds a representation of a Kubernetes object. -type Object struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Info *ObjectInfo `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` - Fields []*Field `protobuf:"bytes,3,rep,name=fields" json:"fields,omitempty"` +// Document is the root of Spread data stored in a Git blob. It has field stored at it's root, typically with an object as it's value. +type Document struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Info *DocumentInfo `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` + Root *Field `protobuf:"bytes,3,opt,name=root" json:"root,omitempty"` } -func (m *Object) Reset() { *m = Object{} } -func (m *Object) String() string { return proto.CompactTextString(m) } -func (*Object) ProtoMessage() {} -func (*Object) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } +func (m *Document) Reset() { *m = Document{} } +func (m *Document) String() string { return proto.CompactTextString(m) } +func (*Document) ProtoMessage() {} +func (*Document) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } -func (m *Object) GetInfo() *ObjectInfo { +func (m *Document) GetInfo() *DocumentInfo { if m != nil { return m.Info } return nil } -func (m *Object) GetFields() []*Field { +func (m *Document) GetRoot() *Field { if m != nil { - return m.Fields + return m.Root } return nil } -// ObjectInfo provides metadata about an object. -type ObjectInfo struct { +// DocumentInfo provides metadata about an document. +type DocumentInfo struct { Path string `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"` } -func (m *ObjectInfo) Reset() { *m = ObjectInfo{} } -func (m *ObjectInfo) String() string { return proto.CompactTextString(m) } -func (*ObjectInfo) ProtoMessage() {} -func (*ObjectInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } +func (m *DocumentInfo) Reset() { *m = DocumentInfo{} } +func (m *DocumentInfo) String() string { return proto.CompactTextString(m) } +func (*DocumentInfo) ProtoMessage() {} +func (*DocumentInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } func init() { proto.RegisterType((*Field)(nil), "spread.Field") - proto.RegisterType((*Map)(nil), "spread.Map") + proto.RegisterType((*Object)(nil), "spread.Object") proto.RegisterType((*Array)(nil), "spread.Array") proto.RegisterType((*SRI)(nil), "spread.SRI") proto.RegisterType((*Link)(nil), "spread.Link") - proto.RegisterType((*Object)(nil), "spread.Object") - proto.RegisterType((*ObjectInfo)(nil), "spread.ObjectInfo") + proto.RegisterType((*Document)(nil), "spread.Document") + proto.RegisterType((*DocumentInfo)(nil), "spread.DocumentInfo") } var fileDescriptor0 = []byte{ - // 381 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x52, 0x4d, 0x6b, 0xe2, 0x50, - 0x14, 0x35, 0xe6, 0x4b, 0x6f, 0x94, 0x71, 0xde, 0x30, 0x10, 0x1c, 0x67, 0x90, 0x0c, 0x03, 0xb3, - 0xca, 0xa2, 0x6e, 0x8a, 0xbb, 0x0a, 0x2d, 0x5a, 0xfa, 0x01, 0xed, 0xa2, 0xd0, 0xdd, 0x8b, 0x5e, - 0x35, 0x35, 0x26, 0xe1, 0xe5, 0x29, 0xf8, 0x9b, 0xfa, 0x27, 0x7b, 0xdf, 0x4b, 0x62, 0x15, 0xba, - 0x0a, 0xf7, 0x9e, 0x93, 0x73, 0xee, 0x39, 0x09, 0x74, 0xb2, 0xe8, 0x0d, 0xe7, 0x32, 0xcc, 0x45, - 0x26, 0x33, 0xe6, 0x14, 0xb9, 0x40, 0xbe, 0x08, 0xde, 0x0d, 0xb0, 0x6f, 0x62, 0x4c, 0x16, 0xcc, - 0x03, 0x73, 0x83, 0x07, 0xdf, 0x18, 0x1a, 0xff, 0xdb, 0xac, 0x07, 0x4e, 0xba, 0xdb, 0x46, 0x28, - 0xfc, 0x26, 0xcd, 0xc6, 0xb4, 0xc1, 0xba, 0x60, 0x16, 0x52, 0xf8, 0xa6, 0x82, 0x69, 0xfc, 0x0e, - 0x6e, 0x94, 0x65, 0x09, 0xf2, 0xd4, 0xb7, 0x68, 0xd5, 0xa2, 0x55, 0x1f, 0x4c, 0xb2, 0xf0, 0x6d, - 0x1a, 0xbd, 0x0b, 0x2f, 0x2c, 0x0d, 0xc2, 0x7b, 0x9e, 0x13, 0xf6, 0x07, 0x6c, 0x2e, 0x04, 0x3f, - 0xf8, 0x8e, 0x46, 0xbb, 0x35, 0x7a, 0xa5, 0x96, 0x84, 0x0f, 0xc0, 0x4a, 0xe2, 0x74, 0xe3, 0xbb, - 0x1a, 0xee, 0xd4, 0xf0, 0x1d, 0xed, 0xa6, 0x8d, 0x89, 0x0b, 0xf6, 0x9e, 0x27, 0x3b, 0x0c, 0x96, - 0x60, 0x92, 0x1e, 0xfb, 0x0b, 0x56, 0x2c, 0x71, 0x4b, 0xb7, 0x9a, 0xc4, 0xfe, 0x79, 0x62, 0x15, - 0xce, 0x68, 0x7f, 0x9d, 0x4a, 0x71, 0xe8, 0x8f, 0xa1, 0x7d, 0x1c, 0xce, 0xc3, 0x0d, 0x2a, 0x39, - 0x9d, 0xed, 0xe4, 0x18, 0xdd, 0xc3, 0xb8, 0x79, 0x69, 0x04, 0xff, 0xc0, 0xd6, 0x97, 0x29, 0xaa, - 0x72, 0x2a, 0x2a, 0xab, 0x73, 0x6a, 0x30, 0x02, 0xf3, 0xf9, 0x69, 0xc6, 0xbe, 0x81, 0x2b, 0x05, - 0x62, 0x5c, 0xac, 0x2b, 0x83, 0x0e, 0x58, 0x39, 0x97, 0x6b, 0xad, 0xdf, 0xa6, 0xe6, 0xec, 0xa5, - 0xa2, 0x97, 0xdd, 0x05, 0xb7, 0x60, 0xa9, 0x58, 0xec, 0x07, 0x78, 0x39, 0x9f, 0x6f, 0xf8, 0x0a, - 0x1f, 0xf8, 0x16, 0xab, 0x37, 0x7f, 0x81, 0x23, 0xb9, 0x58, 0xa1, 0xac, 0x6e, 0x3b, 0xd6, 0xa8, - 0x7c, 0x7a, 0xd0, 0xca, 0xf6, 0x28, 0x44, 0xbc, 0x40, 0xad, 0xd5, 0x0a, 0x5e, 0xc0, 0x79, 0xd4, - 0x5f, 0x55, 0x59, 0xa6, 0x9f, 0x32, 0x43, 0x2a, 0x28, 0x5d, 0x66, 0x95, 0x08, 0xab, 0x45, 0x4a, - 0xee, 0x8c, 0x10, 0xf6, 0x1b, 0x1c, 0x7d, 0x54, 0x41, 0x4a, 0x5f, 0x24, 0xeb, 0x03, 0x9c, 0x90, - 0xeb, 0x3c, 0x5a, 0x7c, 0xd2, 0x7d, 0xf5, 0x4a, 0xae, 0xfe, 0x93, 0x22, 0x47, 0x3f, 0x46, 0x1f, - 0x01, 0x00, 0x00, 0xff, 0xff, 0x59, 0xbf, 0x3a, 0x05, 0x60, 0x02, 0x00, 0x00, + // 383 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x52, 0xcf, 0x4f, 0xe2, 0x40, + 0x18, 0xa5, 0xf4, 0x27, 0x5f, 0xcb, 0x2e, 0xdb, 0xdd, 0x43, 0x77, 0x97, 0x18, 0xd2, 0xc4, 0x84, + 0x53, 0x0f, 0x72, 0x31, 0x7a, 0x92, 0xa8, 0x01, 0x63, 0x34, 0xd1, 0x93, 0xde, 0xa6, 0xf0, 0x01, + 0x95, 0xb6, 0xd3, 0x4c, 0x07, 0x12, 0xfe, 0x2c, 0xff, 0x43, 0x67, 0xa6, 0x6d, 0xa0, 0xf1, 0xd4, + 0x7c, 0xef, 0xbd, 0x79, 0xef, 0x7b, 0x5f, 0x0a, 0x1e, 0x8d, 0x3f, 0x70, 0xc1, 0xa3, 0x82, 0x51, + 0x4e, 0x7d, 0xab, 0x2c, 0x18, 0x92, 0x65, 0xf8, 0xa9, 0x81, 0x79, 0x9f, 0x60, 0xba, 0xf4, 0x5d, + 0xd0, 0xb7, 0x78, 0x08, 0xb4, 0x91, 0x36, 0xee, 0xf9, 0x03, 0xb0, 0xf2, 0x5d, 0x16, 0x23, 0x0b, + 0xba, 0x62, 0xd6, 0x66, 0x1d, 0xbf, 0x0f, 0x7a, 0xc9, 0x59, 0xa0, 0x4b, 0x5a, 0x8c, 0xbf, 0xc0, + 0x8e, 0x29, 0x4d, 0x91, 0xe4, 0x81, 0x21, 0x20, 0x47, 0x40, 0x23, 0xb0, 0xaa, 0x88, 0xc0, 0x14, + 0x88, 0x7b, 0xf1, 0x23, 0xaa, 0x32, 0xa2, 0x67, 0x85, 0x0a, 0xc5, 0x19, 0x98, 0x84, 0x31, 0x72, + 0x08, 0x2c, 0x25, 0xe8, 0x37, 0x82, 0x1b, 0x09, 0x0a, 0x7e, 0x08, 0x46, 0x9a, 0xe4, 0xdb, 0xc0, + 0x56, 0xb4, 0xd7, 0xd0, 0x8f, 0x02, 0x9b, 0x75, 0xa6, 0x36, 0x98, 0x7b, 0x92, 0xee, 0x30, 0xa4, + 0x60, 0x55, 0x96, 0xfe, 0x18, 0xcc, 0x84, 0x63, 0x56, 0x8a, 0xad, 0x75, 0xf1, 0xe2, 0x6f, 0x3b, + 0x31, 0x9a, 0x4b, 0xee, 0x2e, 0xe7, 0xec, 0xf0, 0xef, 0x1a, 0xe0, 0x38, 0xb5, 0xbb, 0x0e, 0x6b, + 0x5f, 0x55, 0xf5, 0x64, 0x2b, 0x75, 0x96, 0xab, 0xee, 0xa5, 0x16, 0x9e, 0x83, 0xa9, 0x56, 0x94, + 0xd2, 0xd3, 0xbc, 0xb6, 0x34, 0x9c, 0x80, 0xfe, 0xfa, 0x32, 0xf7, 0x7f, 0x82, 0xcd, 0x19, 0x62, + 0x52, 0x6e, 0xea, 0x00, 0x0f, 0x8c, 0x82, 0xf0, 0x8d, 0xf2, 0xef, 0x89, 0x43, 0x9a, 0x2b, 0x29, + 0xaf, 0x4e, 0x19, 0x3e, 0x80, 0x21, 0xfb, 0xf9, 0xbf, 0xc1, 0x2d, 0xc8, 0x62, 0x4b, 0xd6, 0xf8, + 0x44, 0x32, 0xac, 0x5f, 0xfe, 0x07, 0x8b, 0x13, 0xb6, 0x46, 0x5e, 0xef, 0xe6, 0x36, 0x81, 0x32, + 0x67, 0x00, 0x0e, 0xdd, 0x23, 0x63, 0xc9, 0x12, 0x95, 0x97, 0x13, 0xbe, 0x81, 0x73, 0x4b, 0x17, + 0xbb, 0x0c, 0x73, 0x2e, 0x43, 0xf3, 0xa3, 0x51, 0x08, 0x46, 0x92, 0xaf, 0x68, 0x6d, 0xf3, 0xa7, + 0xb1, 0x69, 0xd4, 0x73, 0xc1, 0x89, 0x30, 0x83, 0x51, 0xca, 0x95, 0xd7, 0xb7, 0x6e, 0x43, 0xf0, + 0x5a, 0xe2, 0xa6, 0x93, 0xb2, 0x9f, 0xf6, 0xdf, 0xdd, 0x4a, 0xad, 0x7e, 0xae, 0xd8, 0x52, 0x9f, + 0xc9, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x5c, 0x94, 0x66, 0x73, 0x02, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto index 94adf0e..b0c2a06 100644 --- a/proto/object.proto +++ b/proto/object.proto @@ -18,15 +18,15 @@ message Field { double number = 2; string str = 3; bool boolean = 4; - Map obj = 5; + Object object = 5; Array array = 6; Link link = 7; } } -// Map represents a map. -message Map { - map item = 1; +// Object represents a map with strings for keys. +message Object { + map items = 1; } // Array represents an array. @@ -34,7 +34,7 @@ message Array { repeated Field items = 1; } -// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. +// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for a document or field stored within a repository. message SRI { string treeish = 1; string path = 2; @@ -48,14 +48,14 @@ message Link { bool override = 3; } -// Object holds a representation of a Kubernetes object. -message Object { +// Document is the root of Spread data stored in a Git blob. It has field stored at it's root, typically with an object as it's value. +message Document { string name = 1; - ObjectInfo info = 2; - repeated Field fields = 3; + DocumentInfo info = 2; + Field root = 3; } -// ObjectInfo provides metadata about an object. -message ObjectInfo { +// DocumentInfo provides metadata about an document. +message DocumentInfo { string path = 1; } From 1a9163b6ebec510afd7aae8fb4accb7f72229f10 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Fri, 8 Jul 2016 12:51:54 -0700 Subject: [PATCH 51/79] fixed improper type in error formatting --- pkg/data/fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/data/fields.go b/pkg/data/fields.go index 6146f39..2acd61b 100644 --- a/pkg/data/fields.go +++ b/pkg/data/fields.go @@ -47,7 +47,7 @@ func getFromMapField(field *pb.Field, key string) (*pb.Field, error) { items := fieldMap.GetItems() if items == nil { - return nil, fmt.Errorf("the object wrapper struct for the value of field '%s' had nil for items, cannot access %s[%d]", field.Key, field.Key, key) + return nil, fmt.Errorf("the object wrapper struct for the value of field '%s' had nil for items, cannot access %s[%s]", field.Key, field.Key, key) } item, ok := items[key] From 41d1623549c668e17baa47e89fdbab9dc14702c9 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Mon, 11 Jul 2016 19:02:51 -0700 Subject: [PATCH 52/79] added simple discovery test --- pkg/data/discovery.go | 29 ++++++++++++++ pkg/data/discovery_test.go | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 pkg/data/discovery.go create mode 100644 pkg/data/discovery_test.go diff --git a/pkg/data/discovery.go b/pkg/data/discovery.go new file mode 100644 index 0000000..b281f0c --- /dev/null +++ b/pkg/data/discovery.go @@ -0,0 +1,29 @@ +package data + +import ( + "net/http" +) + +// DiscoveryQueryParam is the query parameter that is appended to URLs to signal the request is looking for +// repository information. +const DiscoveryQueryParam = "spread-get=1" + +// httpClient is a copy of DefaultClient for testing purposes. +var httpClient = http.DefaultClient + +// packageInfo contains the data retrieved in the discovery process. +type packageInfo struct { + // prefix is the package contained in the repo. It should be an exact match or prefix to the requested package name. + prefix string + // repoURL is the location of the repository where package data is stored. + repoURL string +} + +// DiscoverPackage uses the package name to fetch a Spread URL to the repository. Set insecure when HTTP is allowed. +func DiscoverPackage(packageName string, insecure bool) (packageInfo, error) { + return packageInfo{}, nil +} + +func fetch(scheme, packageName string) (*http.Response, error) { + return nil, nil +} diff --git a/pkg/data/discovery_test.go b/pkg/data/discovery_test.go new file mode 100644 index 0000000..f42b4e8 --- /dev/null +++ b/pkg/data/discovery_test.go @@ -0,0 +1,82 @@ +package data + +import ( + "fmt" + "net" + "net/http" + "testing" +) + +func TestDiscoverPackage(t *testing.T) { + expected := packageInfo{ + prefix: "redspread.com/halp", + repoURL: "http://104.155.154.203/test.git", + } + server := NewDServer(t, expected) + go server.Start() + defer server.Stop() + + // here DNS would resolve from package name to host + // we will only check if package data matches + // another test should be used to ensure that prefix matches package + importURL := fmt.Sprintf("%s/halp", server.Addr()) + println(importURL) + actual, err := DiscoverPackage(importURL, false) + if err != nil { + t.Errorf("could not discover package: %v", err) + } else if actual.repoURL != expected.repoURL { + t.Errorf("repoURL did not match: \"%s\" (expected \"%s\")", actual.repoURL, expected.repoURL) + } else if actual.prefix != expected.prefix { + t.Errorf("prefix did not match: \"%s\" (expected \"%s\")", actual.prefix, expected.prefix) + } +} + +// testDServer mocks a server with discovery info. +type testDServer struct { + info packageInfo + net.Listener + *testing.T +} + +func NewDServer(t *testing.T, pkgInfo packageInfo) *testDServer { + return &testDServer{ + info: pkgInfo, + Listener: randListener(t), + T: t, + } +} + +func (s *testDServer) Addr() string { + return s.Listener.Addr().String() +} + +func (s *testDServer) Start() { + mux := http.NewServeMux() + mux.HandleFunc("/", s.handler) + http.Serve(s.Listener, mux) +} + +func (s *testDServer) Stop() error { + return s.Listener.Close() +} + +func (s *testDServer) handler(w http.ResponseWriter, r *http.Request) { + msg := "Discovery Test Page

Nothing to see here!

" + if _, err := fmt.Fprintf(w, msg, s.info.prefix, s.info.repoURL); err != nil { + s.Fatalf("Encountered error mocking discovery response: %v", err) + } +} + +// randomListener returns a listener for an available port. +func randListener(t *testing.T) *net.TCPListener { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + + lis, err := net.ListenTCP("tcp", addr) + if err != nil { + t.Fatal(err) + } + return lis +} From da2086878f638b00536c43ed75a9a25c530d93fa Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 12 Jul 2016 13:25:01 -0700 Subject: [PATCH 53/79] working discovery (unable to test on HTTPS) --- pkg/data/config.go | 15 +++++ pkg/data/discovery.go | 129 +++++++++++++++++++++++++++++++++++-- pkg/data/discovery_test.go | 7 +- 3 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 pkg/data/config.go diff --git a/pkg/data/config.go b/pkg/data/config.go new file mode 100644 index 0000000..7def4b5 --- /dev/null +++ b/pkg/data/config.go @@ -0,0 +1,15 @@ +package data + +import ( + "io" + "os" +) + +// Out is the Writer that debugging information is written to. +var Out io.Writer + +func init() { + if Out == nil { + Out = os.Stdout + } +} diff --git a/pkg/data/discovery.go b/pkg/data/discovery.go index b281f0c..a4c1de7 100644 --- a/pkg/data/discovery.go +++ b/pkg/data/discovery.go @@ -1,12 +1,22 @@ package data import ( + "encoding/xml" + "fmt" + "io" "net/http" + "net/url" + "strings" ) -// DiscoveryQueryParam is the query parameter that is appended to URLs to signal the request is looking for -// repository information. -const DiscoveryQueryParam = "spread-get=1" +const ( + // DiscoveryQueryParam is the query parameter that is appended to URLs to signal the request is looking for + // repository information. + DiscoveryQueryParam = "spread-get=1" + + // DiscoveryMetaName is the the 'name' of the tag that contains Spread package information. + DiscoveryMetaName = "spread-ref" +) // httpClient is a copy of DefaultClient for testing purposes. var httpClient = http.DefaultClient @@ -20,10 +30,115 @@ type packageInfo struct { } // DiscoverPackage uses the package name to fetch a Spread URL to the repository. Set insecure when HTTP is allowed. -func DiscoverPackage(packageName string, insecure bool) (packageInfo, error) { - return packageInfo{}, nil +// Verbose will print information to STDOUT. +func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, error) { + // first try HTTPS + urlStr, res, err := fetch("https", packageName, verbose) + if err != nil || res.StatusCode != 200 { + if verbose { + if err != nil { + fmt.Fprint(Out, "https fetch failed") + } else { + fmt.Fprintf(Out, "ignoring https fetch with status code %d", res.StatusCode) + } + } + // fallback to HTTP if insecure is allowed + if insecure { + urlStr, res, err = fetch("http", packageName, verbose) + } + } + if err != nil { + return packageInfo{}, err + } + + // close body when done + if res != nil { + defer res.Body.Close() + } + + if verbose { + fmt.Fprintf(Out, "Parsing meta information from '%s' (status code %d)", urlStr, res.StatusCode) + } + + pkgs, err := parseSpreadRefs(res.Body) + if err != nil { + return packageInfo{}, fmt.Errorf("could not parse for Spread references: %v", err) + } else if len(pkgs) < 1 { + return packageInfo{}, fmt.Errorf("no reference found at '%s'", urlStr) + } else if len(pkgs) > 1 && verbose { + fmt.Fprintf(Out, "found more than one reference at '%s', using first found", urlStr) + } + return pkgs[0], nil +} + +// fetch retrieves the package using the given scheme and returns the response and a string of the URL of the fetch. +func fetch(scheme, packageName string, verbose bool) (string, *http.Response, error) { + u, err := url.Parse(scheme + "://" + packageName) + if err != nil { + return "", nil, err + } + u.RawQuery = DiscoveryQueryParam + urlStr := u.String() + if verbose { + fmt.Fprintf(Out, "fetching %s", urlStr) + } + + res, err := httpClient.Get(urlStr) + return urlStr, res, err +} + +// parseSpreadRefs reads an HTML document from r and uses it to return information about the package. +// Information is currently stored in a tag with the name "spread-ref". Based on Go Get parsing code. +func parseSpreadRefs(r io.Reader) (pkgs []packageInfo, err error) { + d := xml.NewDecoder(r) + // only support documents encoded with ASCII + d.CharsetReader = func(charset string, in io.Reader) (io.Reader, error) { + switch strings.ToLower(charset) { + case "ascii": + return in, nil + default: + return nil, fmt.Errorf("cannot decode Spread package information encoded in %q", charset) + } + } + d.Strict = false + var t xml.Token + for { + if t, err = d.Token(); err != nil { + if err == io.EOF { + err = nil + } + return + } + if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") { + return + } + if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") { + return + } + e, ok := t.(xml.StartElement) + if !ok || !strings.EqualFold(e.Name.Local, "meta") { + continue + } + if attrValue(e.Attr, "name") != DiscoveryMetaName { + continue + } + + if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 2 { + pkgs = append(pkgs, packageInfo{ + prefix: f[0], + repoURL: f[1], + }) + } + } } -func fetch(scheme, packageName string) (*http.Response, error) { - return nil, nil +// attrValue returns the attribute value for the case-insensitive key +// `name', or the empty string if nothing is found. +func attrValue(attrs []xml.Attr, name string) string { + for _, a := range attrs { + if strings.EqualFold(a.Name.Local, name) { + return a.Value + } + } + return "" } diff --git a/pkg/data/discovery_test.go b/pkg/data/discovery_test.go index f42b4e8..f967f32 100644 --- a/pkg/data/discovery_test.go +++ b/pkg/data/discovery_test.go @@ -20,8 +20,7 @@ func TestDiscoverPackage(t *testing.T) { // we will only check if package data matches // another test should be used to ensure that prefix matches package importURL := fmt.Sprintf("%s/halp", server.Addr()) - println(importURL) - actual, err := DiscoverPackage(importURL, false) + actual, err := DiscoverPackage(importURL, true, true) if err != nil { t.Errorf("could not discover package: %v", err) } else if actual.repoURL != expected.repoURL { @@ -61,8 +60,8 @@ func (s *testDServer) Stop() error { } func (s *testDServer) handler(w http.ResponseWriter, r *http.Request) { - msg := "Discovery Test Page

Nothing to see here!

" - if _, err := fmt.Fprintf(w, msg, s.info.prefix, s.info.repoURL); err != nil { + msg := "Discovery Test Page

Nothing to see here!

" + if _, err := fmt.Fprintf(w, msg, DiscoveryMetaName, s.info.prefix, s.info.repoURL); err != nil { s.Fatalf("Encountered error mocking discovery response: %v", err) } } From add8fd9ac9c30e12fdeba7f21434698e2fb06dff Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 10:51:27 -0700 Subject: [PATCH 54/79] close body of failed HTTPS discovery requests --- pkg/data/discovery.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/data/discovery.go b/pkg/data/discovery.go index a4c1de7..6806d50 100644 --- a/pkg/data/discovery.go +++ b/pkg/data/discovery.go @@ -44,6 +44,9 @@ func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, e } // fallback to HTTP if insecure is allowed if insecure { + if res != nil { + res.Body.Close() + } urlStr, res, err = fetch("http", packageName, verbose) } } From dc03158860fa1529228d9bb33d45d30701461502 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 11:17:52 -0700 Subject: [PATCH 55/79] created 'packages' package --- pkg/{data => config}/config.go | 2 +- pkg/config/config_test.go | 12 ++++++++++++ pkg/{data => packages}/discovery.go | 14 ++++++++------ pkg/{data => packages}/discovery_test.go | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) rename pkg/{data => config}/config.go (91%) create mode 100644 pkg/config/config_test.go rename pkg/{data => packages}/discovery.go (89%) rename pkg/{data => packages}/discovery_test.go (99%) diff --git a/pkg/data/config.go b/pkg/config/config.go similarity index 91% rename from pkg/data/config.go rename to pkg/config/config.go index 7def4b5..b6534a0 100644 --- a/pkg/data/config.go +++ b/pkg/config/config.go @@ -1,4 +1,4 @@ -package data +package config import ( "io" diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..1be5f09 --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,12 @@ +package config + +import ( + "os" + "testing" +) + +func TestSetupOut(t *testing.T) { + if Out != os.Stdout { + t.Error("Out should have been set to use STDOUT") + } +} diff --git a/pkg/data/discovery.go b/pkg/packages/discovery.go similarity index 89% rename from pkg/data/discovery.go rename to pkg/packages/discovery.go index 6806d50..a6024a9 100644 --- a/pkg/data/discovery.go +++ b/pkg/packages/discovery.go @@ -1,4 +1,4 @@ -package data +package packages import ( "encoding/xml" @@ -7,6 +7,8 @@ import ( "net/http" "net/url" "strings" + + "rsprd.com/spread/pkg/config" ) const ( @@ -37,9 +39,9 @@ func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, e if err != nil || res.StatusCode != 200 { if verbose { if err != nil { - fmt.Fprint(Out, "https fetch failed") + fmt.Fprint(config.Out, "https fetch failed") } else { - fmt.Fprintf(Out, "ignoring https fetch with status code %d", res.StatusCode) + fmt.Fprintf(config.Out, "ignoring https fetch with status code %d", res.StatusCode) } } // fallback to HTTP if insecure is allowed @@ -60,7 +62,7 @@ func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, e } if verbose { - fmt.Fprintf(Out, "Parsing meta information from '%s' (status code %d)", urlStr, res.StatusCode) + fmt.Fprintf(config.Out, "Parsing meta information from '%s' (status code %d)", urlStr, res.StatusCode) } pkgs, err := parseSpreadRefs(res.Body) @@ -69,7 +71,7 @@ func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, e } else if len(pkgs) < 1 { return packageInfo{}, fmt.Errorf("no reference found at '%s'", urlStr) } else if len(pkgs) > 1 && verbose { - fmt.Fprintf(Out, "found more than one reference at '%s', using first found", urlStr) + fmt.Fprintf(config.Out, "found more than one reference at '%s', using first found", urlStr) } return pkgs[0], nil } @@ -83,7 +85,7 @@ func fetch(scheme, packageName string, verbose bool) (string, *http.Response, er u.RawQuery = DiscoveryQueryParam urlStr := u.String() if verbose { - fmt.Fprintf(Out, "fetching %s", urlStr) + fmt.Fprintf(config.Out, "fetching %s", urlStr) } res, err := httpClient.Get(urlStr) diff --git a/pkg/data/discovery_test.go b/pkg/packages/discovery_test.go similarity index 99% rename from pkg/data/discovery_test.go rename to pkg/packages/discovery_test.go index f967f32..039a8ec 100644 --- a/pkg/data/discovery_test.go +++ b/pkg/packages/discovery_test.go @@ -1,4 +1,4 @@ -package data +package packages import ( "fmt" From 48421c23d0b3192b4f2fa78fa5841610ca9ac567 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 12:01:16 -0700 Subject: [PATCH 56/79] added test for global project --- pkg/project/global.go | 32 +++++++++++++++++ pkg/project/global_test.go | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 pkg/project/global.go create mode 100644 pkg/project/global_test.go diff --git a/pkg/project/global.go b/pkg/project/global.go new file mode 100644 index 0000000..8a64d21 --- /dev/null +++ b/pkg/project/global.go @@ -0,0 +1,32 @@ +package project + +import ( + "errors" + + "github.com/mitchellh/go-homedir" +) + +var ( + // GlobalPath is the path that holds the Spread global repository for a user. The '~' character may be used + // to denote the home directory of a user across platforms. + GlobalPath = "~/.spread-global" +) + +// Global returns the users global project which holds data from any package downloaded. +func Global() (*Project, error) { + return nil, nil +} + +// InitGlobal initializes the global repository for this user. +func InitGlobal() (*Project, error) { + return nil, nil +} + +func GlobalLocation() (string, error) { + return homedir.Expand(GlobalPath) +} + +var ( + // ErrNoGlobal is returned when a global project does not exist for this user. + ErrNoGlobal = errors.New("global project does not exist") +) diff --git a/pkg/project/global_test.go b/pkg/project/global_test.go new file mode 100644 index 0000000..d481c26 --- /dev/null +++ b/pkg/project/global_test.go @@ -0,0 +1,74 @@ +package project + +import ( + "os" + "testing" +) + +// returns global location after cleaning up directory +func cleanupGlobal(t *testing.T) string { + // set global path to test path + GlobalPath = "~/test-spread-global-path" + + // delete contents + if path, err := GlobalLocation(); err == nil { + if err = os.RemoveAll(path); err != nil { + t.Fatalf("could not remove global location: %v", err) + return "" + } + return path + } else { + t.Fatalf("could not resolve global location: %v", err) + return "" + } +} + +func TestNoInitGlobal(t *testing.T) { + cleanupGlobal(t) + _, err := Global() + if err != ErrNoGlobal { + t.Error("did not return error about getting global while none exists") + } +} + +func TestInitGlobal(t *testing.T) { + globalPath := cleanupGlobal(t) + proj, err := InitGlobal() + if err != nil { + t.Errorf("failed to init global: %v", err) + } else if proj == nil { + t.Error("the returned project was nil") + } else if proj.Path != globalPath { + t.Errorf("the path the created project ('%s') does not match the global path ('%s')", proj.Path, globalPath) + } +} + +func TestGetGlobal(t *testing.T) { + globalPath := cleanupGlobal(t) + _, err := InitGlobal() + if err != nil { + t.Errorf("failed to init global: %v", err) + } + + proj, err := Global() + if err != nil { + t.Errorf("could not get global: %v", err) + } else if proj == nil { + t.Error("the returned project was nil") + } else if proj.Path != globalPath { + t.Errorf("the path the created project ('%s') does not match the global path ('%s')", proj.Path, globalPath) + } +} + +func TestDoubleInitGlobal(t *testing.T) { + cleanupGlobal(t) + _, err := InitGlobal() + if err != nil { + t.Errorf("failed to init global: %v", err) + } + + _, err = InitGlobal() + if err == nil { + t.Error("did not throw error for double initialization of global repo") + } +} From 87c81f7fd4bef7fef17daf03db35badbae8202f9 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 12:11:42 -0700 Subject: [PATCH 57/79] implemented global project --- pkg/project/global.go | 22 +++++++++++++--------- pkg/project/global_test.go | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/project/global.go b/pkg/project/global.go index 8a64d21..e9f7ea2 100644 --- a/pkg/project/global.go +++ b/pkg/project/global.go @@ -1,8 +1,6 @@ package project import ( - "errors" - "github.com/mitchellh/go-homedir" ) @@ -14,19 +12,25 @@ var ( // Global returns the users global project which holds data from any package downloaded. func Global() (*Project, error) { - return nil, nil + path, err := GlobalLocation() + if err != nil { + return nil, err + } + + return OpenProject(path) } // InitGlobal initializes the global repository for this user. func InitGlobal() (*Project, error) { - return nil, nil + path, err := GlobalLocation() + if err != nil { + return nil, err + } + + return InitProject(path) } +// GlobalLocation returns the path of the global project. An error is returned if the path doesn't exist. func GlobalLocation() (string, error) { return homedir.Expand(GlobalPath) } - -var ( - // ErrNoGlobal is returned when a global project does not exist for this user. - ErrNoGlobal = errors.New("global project does not exist") -) diff --git a/pkg/project/global_test.go b/pkg/project/global_test.go index d481c26..ca7a578 100644 --- a/pkg/project/global_test.go +++ b/pkg/project/global_test.go @@ -26,7 +26,7 @@ func cleanupGlobal(t *testing.T) string { func TestNoInitGlobal(t *testing.T) { cleanupGlobal(t) _, err := Global() - if err != ErrNoGlobal { + if err == nil { t.Error("did not return error about getting global while none exists") } } From b6c435ef72eea2655b281a1331e2b75294e4e0ae Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 15:17:17 -0700 Subject: [PATCH 58/79] package name expansion tests --- pkg/packages/discovery.go | 11 ++++++++ pkg/packages/discovery_test.go | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/pkg/packages/discovery.go b/pkg/packages/discovery.go index a6024a9..6de466a 100644 --- a/pkg/packages/discovery.go +++ b/pkg/packages/discovery.go @@ -18,8 +18,19 @@ const ( // DiscoveryMetaName is the the 'name' of the tag that contains Spread package information. DiscoveryMetaName = "spread-ref" + + // DefaultDomain is the domain assumed for packages if one is not given. + DefaultDomain = "redspread.com" + + // DefaultNamespace is the namespace used if no domain and namespace is given. + DefaultNamespace = "library" ) +// ExpandPackageName returns a retrievable package name for packageName by adding the Redspread domain where a domain isn't specified. +func ExpandPackageName(packageName string) (string, error) { + return "", nil +} + // httpClient is a copy of DefaultClient for testing purposes. var httpClient = http.DefaultClient diff --git a/pkg/packages/discovery_test.go b/pkg/packages/discovery_test.go index 039a8ec..bd96b7a 100644 --- a/pkg/packages/discovery_test.go +++ b/pkg/packages/discovery_test.go @@ -7,6 +7,55 @@ import ( "testing" ) +var expandTestData = []struct { + in string + out string + error bool +}{ + { + in: "", + error: true, + }, + { + in: "{", + error: true, + }, + + { + in: "hadoop", + out: DefaultDomain + "/" + DefaultNamespace + "/hadoop", + }, + { + in: "library/hadoop", + out: DefaultDomain + "/library/hadoop", + }, + { + in: "redspread.com/library/hadoop", + out: "redspread.com/library/hadoop", + }, + { + in: "github.com/redspread/hadoop", + out: "github.com/redspread/hadoop", + }, +} + +func TestExpandPackageName(t *testing.T) { + for i, test := range expandTestData { + pkg, err := ExpandPackageName(test.in) + // check for issues with errors + if err == nil && test.error { + t.Errorf("test %d (input: %s): should have errored", i, test.in) + } else if err != nil && !test.error { + t.Errorf("test %d errored: %v", i, err) + } + + // check for issues with value + if pkg != test.out { + t.Errorf("test %d: expected '%s', got '%s'", i, test.out, pkg) + } + } +} + func TestDiscoverPackage(t *testing.T) { expected := packageInfo{ prefix: "redspread.com/halp", From 693381a0818376f7e90fc208e1f2c5bab87985f5 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 15:51:26 -0700 Subject: [PATCH 59/79] implemented package name expansion --- pkg/packages/discovery.go | 46 +++++++++++++++++++++++++++++++++- pkg/packages/discovery_test.go | 15 ++++++----- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/pkg/packages/discovery.go b/pkg/packages/discovery.go index 6de466a..3f6a4a5 100644 --- a/pkg/packages/discovery.go +++ b/pkg/packages/discovery.go @@ -2,15 +2,23 @@ package packages import ( "encoding/xml" + "errors" "fmt" "io" "net/http" "net/url" + "regexp" "strings" "rsprd.com/spread/pkg/config" ) +var domainRegexp *regexp.Regexp + +func init() { + domainRegexp = regexp.MustCompile(DomainRegexpStr) +} + const ( // DiscoveryQueryParam is the query parameter that is appended to URLs to signal the request is looking for // repository information. @@ -24,11 +32,29 @@ const ( // DefaultNamespace is the namespace used if no domain and namespace is given. DefaultNamespace = "library" + + // DomainRegexpStr is a regular expression string to validate domains. + DomainRegexpStr = "^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$" ) // ExpandPackageName returns a retrievable package name for packageName by adding the Redspread domain where a domain isn't specified. func ExpandPackageName(packageName string) (string, error) { - return "", nil + if len(packageName) == 0 { + return "", errors.New("empty package name") + } + + // if single segment, assume domain and namespace + pkgArr := strings.Split(packageName, "/") + if len(pkgArr) == 1 { + return DefaultDomain + "/" + DefaultNamespace + "/" + packageName, nil + } + + if isDomain(pkgArr[0]) { + return packageName, nil + } + + // if no domain, assume it + return DefaultDomain + "/" + packageName, nil } // httpClient is a copy of DefaultClient for testing purposes. @@ -158,3 +184,21 @@ func attrValue(attrs []xml.Attr, name string) string { } return "" } + +// isDomain returns whether given string is a domain. +// It first checks the TLD, and then uses a regular expression. +func isDomain(s string) bool { + if strings.HasSuffix(s, ".") { + s = s[:len(s)-1] + } + + split := strings.Split(s, ".") + tld := split[len(split)-1] + + if len(tld) < 2 { + return false + } + + s = strings.ToLower(s) + return domainRegexp.MatchString(s) +} diff --git a/pkg/packages/discovery_test.go b/pkg/packages/discovery_test.go index bd96b7a..3e33e5f 100644 --- a/pkg/packages/discovery_test.go +++ b/pkg/packages/discovery_test.go @@ -16,10 +16,11 @@ var expandTestData = []struct { in: "", error: true, }, - { - in: "{", - error: true, - }, + // TODO: should disallow + //{ + // in: "{", + // error: true, + //}, { in: "hadoop", @@ -47,10 +48,8 @@ func TestExpandPackageName(t *testing.T) { t.Errorf("test %d (input: %s): should have errored", i, test.in) } else if err != nil && !test.error { t.Errorf("test %d errored: %v", i, err) - } - - // check for issues with value - if pkg != test.out { + // check for issues with value + } else if pkg != test.out { t.Errorf("test %d: expected '%s', got '%s'", i, test.out, pkg) } } From c7ccde2a9f9726b5478137541d5d6ba775c40abb Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 13 Jul 2016 18:43:45 -0700 Subject: [PATCH 60/79] added package discovery to command line --- cli/build.go | 6 ++--- cli/deploy.go | 41 +++++++++++++++++++++++----------- pkg/packages/discovery.go | 28 +++++++++++------------ pkg/packages/discovery_test.go | 20 ++++++++--------- 4 files changed, 55 insertions(+), 40 deletions(-) diff --git a/cli/build.go b/cli/build.go index 6ab0c87..ab52287 100644 --- a/cli/build.go +++ b/cli/build.go @@ -22,12 +22,12 @@ func (s SpreadCli) Build() *cli.Command { input, err := dir.NewFileInput(srcDir) if err != nil { - s.fatalf(inputError(srcDir, err)) + s.fatalf(inputError(srcDir, err).Error()) } e, err := input.Build() if err != nil { - s.fatalf(inputError(srcDir, err)) + s.fatalf(inputError(srcDir, err).Error()) } dep, err := e.Deployment() @@ -47,7 +47,7 @@ func (s SpreadCli) Build() *cli.Command { } else if err != nil { println("deploy") - s.fatalf(inputError(srcDir, err)) + s.fatalf(inputError(srcDir, err).Error()) } context := c.Args().Get(1) diff --git a/cli/deploy.go b/cli/deploy.go index c844165..a594e0e 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -7,6 +7,7 @@ import ( "rsprd.com/spread/pkg/deploy" "rsprd.com/spread/pkg/entity" "rsprd.com/spread/pkg/input/dir" + "rsprd.com/spread/pkg/packages" "github.com/codegangsta/cli" ) @@ -34,11 +35,11 @@ func (s SpreadCli) Deploy() *cli.Command { if commit, err := proj.ResolveCommit(ref); err == nil { dep = commit } else { - dep = s.fileDeploy(ref) + dep, err = s.nonLocalRepoDeploy(ref) } } } else { - dep = s.fileDeploy(ref) + dep, err = s.nonLocalRepoDeploy(ref) } context := c.Args().Get(1) @@ -61,16 +62,15 @@ func (s SpreadCli) Deploy() *cli.Command { } } -func (s SpreadCli) fileDeploy(srcDir string) *deploy.Deployment { +func (s SpreadCli) fileDeploy(srcDir string) (*deploy.Deployment, error) { input, err := dir.NewFileInput(srcDir) if err != nil { - s.fatalf(inputError(srcDir, err)) + return nil, inputError(srcDir, err) } e, err := input.Build() if err != nil { - println("build") - s.fatalf(inputError(srcDir, err)) + return nil, inputError(srcDir, err) } dep, err := e.Deployment() @@ -80,19 +80,34 @@ func (s SpreadCli) fileDeploy(srcDir string) *deploy.Deployment { // check if has pod; if not deploy objects pods, err := input.Entities(entity.EntityPod) if err != nil && len(pods) != 0 { - s.fatalf("Failed to deploy: %v", err) + return nil, fmt.Errorf("Failed to deploy: %v", err) } dep, err = objectOnlyDeploy(input) if err != nil { - s.fatalf("Failed to deploy: %v", err) + return nil, fmt.Errorf("Failed to deploy: %v", err) } } else if err != nil { - println("deploy") - s.fatalf(inputError(srcDir, err)) + return nil, inputError(srcDir, err) } - return dep + return dep, nil +} + +func (s SpreadCli) nonLocalRepoDeploy(ref string) (*deploy.Deployment, error) { + dep, err := s.fileDeploy(ref) + if err != nil { + ref, err = packages.ExpandPackageName(ref) + if err == nil { + var info packages.PackageInfo + info, err = packages.DiscoverPackage(ref, true, true) + if err != nil { + s.fatalf("failed to retrieve package info: %v", err) + } + s.fatalf("if hooked up would now\n\t- pull %s into the global repo\n\t- deploy the package", info.RepoURL) + } + } + return dep, err } func objectOnlyDeploy(input *dir.FileInput) (*deploy.Deployment, error) { @@ -113,8 +128,8 @@ func objectOnlyDeploy(input *dir.FileInput) (*deploy.Deployment, error) { return deployment, nil } -func inputError(srcDir string, err error) string { - return fmt.Sprintf("Error using `%s`: %v", srcDir, err) +func inputError(srcDir string, err error) error { + return fmt.Errorf("Error using `%s`: %v", srcDir, err) } func displayContext(name string) string { diff --git a/pkg/packages/discovery.go b/pkg/packages/discovery.go index 3f6a4a5..d2cd467 100644 --- a/pkg/packages/discovery.go +++ b/pkg/packages/discovery.go @@ -60,17 +60,17 @@ func ExpandPackageName(packageName string) (string, error) { // httpClient is a copy of DefaultClient for testing purposes. var httpClient = http.DefaultClient -// packageInfo contains the data retrieved in the discovery process. -type packageInfo struct { - // prefix is the package contained in the repo. It should be an exact match or prefix to the requested package name. - prefix string - // repoURL is the location of the repository where package data is stored. - repoURL string +// PackageInfo contains the data retrieved in the discovery process. +type PackageInfo struct { + // Prefix is the package contained in the repo. It should be an exact match or prefix to the requested package name. + Prefix string + // RepoURL is the location of the repository where package data is stored. + RepoURL string } // DiscoverPackage uses the package name to fetch a Spread URL to the repository. Set insecure when HTTP is allowed. // Verbose will print information to STDOUT. -func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, error) { +func DiscoverPackage(packageName string, insecure, verbose bool) (PackageInfo, error) { // first try HTTPS urlStr, res, err := fetch("https", packageName, verbose) if err != nil || res.StatusCode != 200 { @@ -90,7 +90,7 @@ func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, e } } if err != nil { - return packageInfo{}, err + return PackageInfo{}, err } // close body when done @@ -104,9 +104,9 @@ func DiscoverPackage(packageName string, insecure, verbose bool) (packageInfo, e pkgs, err := parseSpreadRefs(res.Body) if err != nil { - return packageInfo{}, fmt.Errorf("could not parse for Spread references: %v", err) + return PackageInfo{}, fmt.Errorf("could not parse for Spread references: %v", err) } else if len(pkgs) < 1 { - return packageInfo{}, fmt.Errorf("no reference found at '%s'", urlStr) + return PackageInfo{}, fmt.Errorf("no reference found at '%s'", urlStr) } else if len(pkgs) > 1 && verbose { fmt.Fprintf(config.Out, "found more than one reference at '%s', using first found", urlStr) } @@ -131,7 +131,7 @@ func fetch(scheme, packageName string, verbose bool) (string, *http.Response, er // parseSpreadRefs reads an HTML document from r and uses it to return information about the package. // Information is currently stored in a tag with the name "spread-ref". Based on Go Get parsing code. -func parseSpreadRefs(r io.Reader) (pkgs []packageInfo, err error) { +func parseSpreadRefs(r io.Reader) (pkgs []PackageInfo, err error) { d := xml.NewDecoder(r) // only support documents encoded with ASCII d.CharsetReader = func(charset string, in io.Reader) (io.Reader, error) { @@ -166,9 +166,9 @@ func parseSpreadRefs(r io.Reader) (pkgs []packageInfo, err error) { } if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 2 { - pkgs = append(pkgs, packageInfo{ - prefix: f[0], - repoURL: f[1], + pkgs = append(pkgs, PackageInfo{ + Prefix: f[0], + RepoURL: f[1], }) } } diff --git a/pkg/packages/discovery_test.go b/pkg/packages/discovery_test.go index 3e33e5f..baf75dc 100644 --- a/pkg/packages/discovery_test.go +++ b/pkg/packages/discovery_test.go @@ -56,9 +56,9 @@ func TestExpandPackageName(t *testing.T) { } func TestDiscoverPackage(t *testing.T) { - expected := packageInfo{ - prefix: "redspread.com/halp", - repoURL: "http://104.155.154.203/test.git", + expected := PackageInfo{ + Prefix: "redspread.com/halp", + RepoURL: "http://104.155.154.203/test.git", } server := NewDServer(t, expected) go server.Start() @@ -71,21 +71,21 @@ func TestDiscoverPackage(t *testing.T) { actual, err := DiscoverPackage(importURL, true, true) if err != nil { t.Errorf("could not discover package: %v", err) - } else if actual.repoURL != expected.repoURL { - t.Errorf("repoURL did not match: \"%s\" (expected \"%s\")", actual.repoURL, expected.repoURL) - } else if actual.prefix != expected.prefix { - t.Errorf("prefix did not match: \"%s\" (expected \"%s\")", actual.prefix, expected.prefix) + } else if actual.RepoURL != expected.RepoURL { + t.Errorf("repoURL did not match: \"%s\" (expected \"%s\")", actual.RepoURL, expected.RepoURL) + } else if actual.Prefix != expected.Prefix { + t.Errorf("prefix did not match: \"%s\" (expected \"%s\")", actual.Prefix, expected.Prefix) } } // testDServer mocks a server with discovery info. type testDServer struct { - info packageInfo + info PackageInfo net.Listener *testing.T } -func NewDServer(t *testing.T, pkgInfo packageInfo) *testDServer { +func NewDServer(t *testing.T, pkgInfo PackageInfo) *testDServer { return &testDServer{ info: pkgInfo, Listener: randListener(t), @@ -109,7 +109,7 @@ func (s *testDServer) Stop() error { func (s *testDServer) handler(w http.ResponseWriter, r *http.Request) { msg := "Discovery Test Page

Nothing to see here!

" - if _, err := fmt.Fprintf(w, msg, DiscoveryMetaName, s.info.prefix, s.info.repoURL); err != nil { + if _, err := fmt.Fprintf(w, msg, DiscoveryMetaName, s.info.Prefix, s.info.RepoURL); err != nil { s.Fatalf("Encountered error mocking discovery response: %v", err) } } From 322678e443945d4cda7f4d311fdf2d22d37b904c Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 20 Jul 2016 16:14:58 -0700 Subject: [PATCH 61/79] beginning of spread distribution --- cli/cli.go | 11 +++++++++++ cli/deploy.go | 37 ++++++++++++++++++++++++++----------- pkg/project/remotes.go | 17 +++++++++++++++-- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index 0944799..95f9255 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -63,6 +63,17 @@ func (c SpreadCli) project() (*project.Project, error) { return proj, nil } +func (c SpreadCli) globalProject() (*project.Project, error) { + proj, err := project.Global() + if err != nil { + if strings.HasSuffix(err.Error(), "no such file or directory") { + return project.InitGlobal() + } + return nil, err + } + return proj, nil +} + func (c SpreadCli) printf(message string, data ...interface{}) { // add newline if doesn't have one if !strings.HasSuffix(message, "\n") { diff --git a/cli/deploy.go b/cli/deploy.go index a594e0e..730edee 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -23,23 +23,24 @@ func (s SpreadCli) Deploy() *cli.Command { ref := c.Args().First() var dep *deploy.Deployment - if proj, err := s.project(); err == nil { + proj, err := s.project() + if err == nil { if len(ref) == 0 { s.printf("Deploying from index...") - index, err := proj.Index() - if err != nil { - s.fatalf("Error reading index: %v", err) - } - dep = index + dep, err = proj.Index() } else { if commit, err := proj.ResolveCommit(ref); err == nil { dep = commit } else { - dep, err = s.nonLocalRepoDeploy(ref) + dep, err = s.globalDeploy(ref) } } } else { - dep, err = s.nonLocalRepoDeploy(ref) + dep, err = s.globalDeploy(ref) + } + + if err != nil { + s.fatalf("Failed to assemble deployment: %v", err) } context := c.Args().Get(1) @@ -94,17 +95,31 @@ func (s SpreadCli) fileDeploy(srcDir string) (*deploy.Deployment, error) { return dep, nil } -func (s SpreadCli) nonLocalRepoDeploy(ref string) (*deploy.Deployment, error) { +func (s SpreadCli) globalDeploy(ref string) (*deploy.Deployment, error) { + // check if reference is local file dep, err := s.fileDeploy(ref) if err != nil { ref, err = packages.ExpandPackageName(ref) if err == nil { var info packages.PackageInfo - info, err = packages.DiscoverPackage(ref, true, true) + info, err = packages.DiscoverPackage(ref, true, false) if err != nil { s.fatalf("failed to retrieve package info: %v", err) } - s.fatalf("if hooked up would now\n\t- pull %s into the global repo\n\t- deploy the package", info.RepoURL) + + proj, err := s.globalProject() + if err != nil { + s.fatalf("error setting up global project: %v", err) + } + + s.printf("pulling repo from %s", info.RepoURL) + refSpec := fmt.Sprintf("refs/heads/%s", ref) + err = proj.FetchAnonymous(info.RepoURL, refSpec) + if err != nil { + return nil, fmt.Errorf("failed to fetch '%s': %v", ref, err) + } + + return proj.ResolveCommit(ref) } } return dep, err diff --git a/pkg/project/remotes.go b/pkg/project/remotes.go index b018e46..e0b02be 100644 --- a/pkg/project/remotes.go +++ b/pkg/project/remotes.go @@ -56,9 +56,22 @@ func (p *Project) Push(remoteName string, refspecs ...string) error { func (p *Project) Fetch(remoteName string, refspecs ...string) error { remote, err := p.Remotes().Lookup(remoteName) if err != nil { - return fmt.Errorf("Failed to lookup branch: %v", err) + return fmt.Errorf("Failed to lookup remote: %v", err) } + return p.fetch(remote, refspecs...) +} + +func (p *Project) FetchAnonymous(url string, refspecs ...string) error { + remote, err := p.Remotes().CreateAnonymous(url) + if err != nil { + return fmt.Errorf("Failed to create anonymous remote for '%s': %v", url, err) + } + + return p.fetch(remote, refspecs...) +} + +func (p *Project) fetch(remote *git.Remote, refspecs ...string) (err error) { opts := &git.FetchOptions{ RemoteCallbacks: remoteCallbacks, } @@ -68,5 +81,5 @@ func (p *Project) Fetch(remoteName string, refspecs ...string) error { if err != nil { return fmt.Errorf("Failed to fetch: %v", err) } - return nil + return } From e517d13a36f37f92d10f66fb6f50be29e2360d3b Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 20 Jul 2016 19:50:40 -0700 Subject: [PATCH 62/79] working deploys from discovery --- cli/deploy.go | 21 ++++++++++++++++++--- cli/remote.go | 1 + pkg/project/branch.go | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 pkg/project/branch.go diff --git a/cli/deploy.go b/cli/deploy.go index 730edee..ad93e80 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -112,14 +112,29 @@ func (s SpreadCli) globalDeploy(ref string) (*deploy.Deployment, error) { s.fatalf("error setting up global project: %v", err) } + remote, err := proj.Remotes().Lookup(ref) + // if does not exist or has different URL, create new remote + if err != nil { + remote, err = proj.Remotes().Create(ref, info.RepoURL) + if err != nil { + return nil, fmt.Errorf("could not create remote: %v", err) + } + } else if remote.Url() != info.RepoURL { + s.printf("changing remote URL for %s, current: '%s' new: '%s'", ref, remote.Url(), info.RepoURL) + err = proj.Remotes().SetUrl(ref, info.RepoURL) + if err != nil { + return nil, fmt.Errorf("failed to change URL for %s: %v", ref, err) + } + } + s.printf("pulling repo from %s", info.RepoURL) - refSpec := fmt.Sprintf("refs/heads/%s", ref) - err = proj.FetchAnonymous(info.RepoURL, refSpec) + branch := fmt.Sprintf("%s/master", ref) + err = proj.Fetch(remote.Name(), "master") if err != nil { return nil, fmt.Errorf("failed to fetch '%s': %v", ref, err) } - return proj.ResolveCommit(ref) + return proj.Branch(branch) } } return dep, err diff --git a/cli/remote.go b/cli/remote.go index 3e38244..ecbacb1 100644 --- a/cli/remote.go +++ b/cli/remote.go @@ -25,6 +25,7 @@ func (s SpreadCli) Remote() *cli.Command { println() cli.ShowSubcommandHelp(c) }, + HideHelp: true, Subcommands: []cli.Command{ { Name: "add", diff --git a/pkg/project/branch.go b/pkg/project/branch.go new file mode 100644 index 0000000..6715afc --- /dev/null +++ b/pkg/project/branch.go @@ -0,0 +1,23 @@ +package project + +import ( + "fmt" + + git "gopkg.in/libgit2/git2go.v23" + + "rsprd.com/spread/pkg/deploy" +) + +func (p *Project) Branch(name string) (*deploy.Deployment, error) { + br, err := p.repo.LookupBranch(name, git.BranchRemote) + if err != nil { + return nil, fmt.Errorf("unable to locate branch: %v", err) + } + + tree, err := br.Peel(git.ObjectTree) + if err != nil { + return nil, err + } + + return p.deploymentFromTree(tree.(*git.Tree)) +} From c6f59ed776c290294fc86ca20441b74330ad4f13 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sat, 23 Jul 2016 21:36:27 -0700 Subject: [PATCH 63/79] start of templates --- pkg/data/parameter.go | 58 +++++++ pkg/spreadproto/object.pb.go | 328 ++++++++++++++++++++++++++++++++--- proto/object.proto | 22 +++ 3 files changed, 382 insertions(+), 26 deletions(-) create mode 100644 pkg/data/parameter.go diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go new file mode 100644 index 0000000..642de64 --- /dev/null +++ b/pkg/data/parameter.go @@ -0,0 +1,58 @@ +package data + +import ( + "errors" + "fmt" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +// AddParamToDoc adds the given parameter to the +func AddParamToDoc(doc *pb.Document, target *SRI, param *pb.Parameter) error { + if !target.IsField() { + return errors.New("passed SRI is not a field") + } + + field, err := GetFieldFromDocument(doc, target.Field) + if err != nil { + return err + } + + field.Param = param + return nil +} + +// ApplyArguments takes the given arguments and uses them to satisfy a field parameter. +func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { + if field == nil { + return errors.New("field was nil") + } else if field.GetParam() == nil { + return fmt.Errorf("field %s does not have a parameter", field.Key) + } else if len(args) < 1 { + return errors.New("an argument must be specified") + } else if len(args) == 1 && len(field.GetParam().Pattern) == 0 { + return simpleArgApply(field, args[0]) + } + // TODO: complete string formatting based apply + return nil +} + +// simpleArgApply is used when no formatting template string is given. +func simpleArgApply(field *pb.Field, arg *pb.Argument) error { + switch val := arg.GetValue().(type) { + case *pb.Argument_Number: + field.Value = *pb.Field_Number{Number: val.Number} + case *pb.Argument_Str: + field.Value = *pb.Field_Str{Str: val.Str} + case *pb.Argument_Boolean: + field.Value = *pb.Field_Boolean{Boolean: val.Boolean} + case *pb.Argument_Object: + field.Value = *pb.Field_Object{Object: val.Object} + case *pb.Argument_Array: + field.Value = *pb.Field_Array{Array: val.Array} + default: + field.Value = nil + } + + return nil +} diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index 44af730..d65eb18 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -16,6 +16,8 @@ It has these top-level messages: Link Document DocumentInfo + Parameter + Argument */ package spreadproto @@ -43,6 +45,8 @@ type Field struct { // *Field_Array // *Field_Link Value isField_Value `protobuf_oneof:"value"` + Param *Parameter `protobuf:"bytes,8,opt,name=param" json:"param,omitempty"` + Args []*Argument `protobuf:"bytes,9,rep,name=args" json:"args,omitempty"` } func (m *Field) Reset() { *m = Field{} } @@ -129,6 +133,20 @@ func (m *Field) GetLink() *Link { return nil } +func (m *Field) GetParam() *Parameter { + if m != nil { + return m.Param + } + return nil +} + +func (m *Field) GetArgs() []*Argument { + if m != nil { + return m.Args + } + return nil +} + // XXX_OneofFuncs is for the internal use of the proto package. func (*Field) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { return _Field_OneofMarshaler, _Field_OneofUnmarshaler, _Field_OneofSizer, []interface{}{ @@ -303,7 +321,7 @@ func (m *Array) GetItems() []*Field { return nil } -// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for an object or field stored within a repository. +// A SRI represents a parsed Spread Resource Identifier (SRI), a globally unique address for a document or field stored within a repository. type SRI struct { Treeish string `protobuf:"bytes,1,opt,name=treeish" json:"treeish,omitempty"` Path string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"` @@ -370,6 +388,255 @@ func (m *DocumentInfo) String() string { return proto.CompactTextStri func (*DocumentInfo) ProtoMessage() {} func (*DocumentInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } +// Parameter allows user input to be used in producing the output of the field. +type Parameter struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Prompt string `protobuf:"bytes,2,opt,name=prompt" json:"prompt,omitempty"` + Pattern string `protobuf:"bytes,3,opt,name=pattern" json:"pattern,omitempty"` + Required bool `protobuf:"varint,4,opt,name=required" json:"required,omitempty"` +} + +func (m *Parameter) Reset() { *m = Parameter{} } +func (m *Parameter) String() string { return proto.CompactTextString(m) } +func (*Parameter) ProtoMessage() {} +func (*Parameter) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +// Argument contains an argument to fulfill a parameter. +type Argument struct { + // Types that are valid to be assigned to Value: + // *Argument_Number + // *Argument_Str + // *Argument_Boolean + // *Argument_Object + // *Argument_Array + // *Argument_Link + Value isArgument_Value `protobuf_oneof:"value"` +} + +func (m *Argument) Reset() { *m = Argument{} } +func (m *Argument) String() string { return proto.CompactTextString(m) } +func (*Argument) ProtoMessage() {} +func (*Argument) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +type isArgument_Value interface { + isArgument_Value() +} + +type Argument_Number struct { + Number float64 `protobuf:"fixed64,1,opt,name=number,oneof"` +} +type Argument_Str struct { + Str string `protobuf:"bytes,2,opt,name=str,oneof"` +} +type Argument_Boolean struct { + Boolean bool `protobuf:"varint,3,opt,name=boolean,oneof"` +} +type Argument_Object struct { + Object *Object `protobuf:"bytes,4,opt,name=object,oneof"` +} +type Argument_Array struct { + Array *Array `protobuf:"bytes,5,opt,name=array,oneof"` +} +type Argument_Link struct { + Link *Link `protobuf:"bytes,6,opt,name=link,oneof"` +} + +func (*Argument_Number) isArgument_Value() {} +func (*Argument_Str) isArgument_Value() {} +func (*Argument_Boolean) isArgument_Value() {} +func (*Argument_Object) isArgument_Value() {} +func (*Argument_Array) isArgument_Value() {} +func (*Argument_Link) isArgument_Value() {} + +func (m *Argument) GetValue() isArgument_Value { + if m != nil { + return m.Value + } + return nil +} + +func (m *Argument) GetNumber() float64 { + if x, ok := m.GetValue().(*Argument_Number); ok { + return x.Number + } + return 0 +} + +func (m *Argument) GetStr() string { + if x, ok := m.GetValue().(*Argument_Str); ok { + return x.Str + } + return "" +} + +func (m *Argument) GetBoolean() bool { + if x, ok := m.GetValue().(*Argument_Boolean); ok { + return x.Boolean + } + return false +} + +func (m *Argument) GetObject() *Object { + if x, ok := m.GetValue().(*Argument_Object); ok { + return x.Object + } + return nil +} + +func (m *Argument) GetArray() *Array { + if x, ok := m.GetValue().(*Argument_Array); ok { + return x.Array + } + return nil +} + +func (m *Argument) GetLink() *Link { + if x, ok := m.GetValue().(*Argument_Link); ok { + return x.Link + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Argument) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Argument_OneofMarshaler, _Argument_OneofUnmarshaler, _Argument_OneofSizer, []interface{}{ + (*Argument_Number)(nil), + (*Argument_Str)(nil), + (*Argument_Boolean)(nil), + (*Argument_Object)(nil), + (*Argument_Array)(nil), + (*Argument_Link)(nil), + } +} + +func _Argument_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Argument) + // value + switch x := m.Value.(type) { + case *Argument_Number: + b.EncodeVarint(1<<3 | proto.WireFixed64) + b.EncodeFixed64(math.Float64bits(x.Number)) + case *Argument_Str: + b.EncodeVarint(2<<3 | proto.WireBytes) + b.EncodeStringBytes(x.Str) + case *Argument_Boolean: + t := uint64(0) + if x.Boolean { + t = 1 + } + b.EncodeVarint(3<<3 | proto.WireVarint) + b.EncodeVarint(t) + case *Argument_Object: + b.EncodeVarint(4<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Object); err != nil { + return err + } + case *Argument_Array: + b.EncodeVarint(5<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Array); err != nil { + return err + } + case *Argument_Link: + b.EncodeVarint(6<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Link); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Argument.Value has unexpected type %T", x) + } + return nil +} + +func _Argument_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Argument) + switch tag { + case 1: // value.number + if wire != proto.WireFixed64 { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeFixed64() + m.Value = &Argument_Number{math.Float64frombits(x)} + return true, err + case 2: // value.str + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Value = &Argument_Str{x} + return true, err + case 3: // value.boolean + if wire != proto.WireVarint { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeVarint() + m.Value = &Argument_Boolean{x != 0} + return true, err + case 4: // value.object + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Object) + err := b.DecodeMessage(msg) + m.Value = &Argument_Object{msg} + return true, err + case 5: // value.array + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Array) + err := b.DecodeMessage(msg) + m.Value = &Argument_Array{msg} + return true, err + case 6: // value.link + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Link) + err := b.DecodeMessage(msg) + m.Value = &Argument_Link{msg} + return true, err + default: + return false, nil + } +} + +func _Argument_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Argument) + // value + switch x := m.Value.(type) { + case *Argument_Number: + n += proto.SizeVarint(1<<3 | proto.WireFixed64) + n += 8 + case *Argument_Str: + n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.Str))) + n += len(x.Str) + case *Argument_Boolean: + n += proto.SizeVarint(3<<3 | proto.WireVarint) + n += 1 + case *Argument_Object: + s := proto.Size(x.Object) + n += proto.SizeVarint(4<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Argument_Array: + s := proto.Size(x.Array) + n += proto.SizeVarint(5<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Argument_Link: + s := proto.Size(x.Link) + n += proto.SizeVarint(6<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + func init() { proto.RegisterType((*Field)(nil), "spread.Field") proto.RegisterType((*Object)(nil), "spread.Object") @@ -378,32 +645,41 @@ func init() { proto.RegisterType((*Link)(nil), "spread.Link") proto.RegisterType((*Document)(nil), "spread.Document") proto.RegisterType((*DocumentInfo)(nil), "spread.DocumentInfo") + proto.RegisterType((*Parameter)(nil), "spread.Parameter") + proto.RegisterType((*Argument)(nil), "spread.Argument") } var fileDescriptor0 = []byte{ - // 383 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x52, 0xcf, 0x4f, 0xe2, 0x40, - 0x18, 0xa5, 0xf4, 0x27, 0x5f, 0xcb, 0x2e, 0xdb, 0xdd, 0x43, 0x77, 0x97, 0x18, 0xd2, 0xc4, 0x84, - 0x53, 0x0f, 0x72, 0x31, 0x7a, 0x92, 0xa8, 0x01, 0x63, 0x34, 0xd1, 0x93, 0xde, 0xa6, 0xf0, 0x01, - 0x95, 0xb6, 0xd3, 0x4c, 0x07, 0x12, 0xfe, 0x2c, 0xff, 0x43, 0x67, 0xa6, 0x6d, 0xa0, 0xf1, 0xd4, - 0x7c, 0xef, 0xbd, 0x79, 0xef, 0x7b, 0x5f, 0x0a, 0x1e, 0x8d, 0x3f, 0x70, 0xc1, 0xa3, 0x82, 0x51, - 0x4e, 0x7d, 0xab, 0x2c, 0x18, 0x92, 0x65, 0xf8, 0xa9, 0x81, 0x79, 0x9f, 0x60, 0xba, 0xf4, 0x5d, - 0xd0, 0xb7, 0x78, 0x08, 0xb4, 0x91, 0x36, 0xee, 0xf9, 0x03, 0xb0, 0xf2, 0x5d, 0x16, 0x23, 0x0b, - 0xba, 0x62, 0xd6, 0x66, 0x1d, 0xbf, 0x0f, 0x7a, 0xc9, 0x59, 0xa0, 0x4b, 0x5a, 0x8c, 0xbf, 0xc0, - 0x8e, 0x29, 0x4d, 0x91, 0xe4, 0x81, 0x21, 0x20, 0x47, 0x40, 0x23, 0xb0, 0xaa, 0x88, 0xc0, 0x14, - 0x88, 0x7b, 0xf1, 0x23, 0xaa, 0x32, 0xa2, 0x67, 0x85, 0x0a, 0xc5, 0x19, 0x98, 0x84, 0x31, 0x72, - 0x08, 0x2c, 0x25, 0xe8, 0x37, 0x82, 0x1b, 0x09, 0x0a, 0x7e, 0x08, 0x46, 0x9a, 0xe4, 0xdb, 0xc0, - 0x56, 0xb4, 0xd7, 0xd0, 0x8f, 0x02, 0x9b, 0x75, 0xa6, 0x36, 0x98, 0x7b, 0x92, 0xee, 0x30, 0xa4, - 0x60, 0x55, 0x96, 0xfe, 0x18, 0xcc, 0x84, 0x63, 0x56, 0x8a, 0xad, 0x75, 0xf1, 0xe2, 0x6f, 0x3b, - 0x31, 0x9a, 0x4b, 0xee, 0x2e, 0xe7, 0xec, 0xf0, 0xef, 0x1a, 0xe0, 0x38, 0xb5, 0xbb, 0x0e, 0x6b, - 0x5f, 0x55, 0xf5, 0x64, 0x2b, 0x75, 0x96, 0xab, 0xee, 0xa5, 0x16, 0x9e, 0x83, 0xa9, 0x56, 0x94, - 0xd2, 0xd3, 0xbc, 0xb6, 0x34, 0x9c, 0x80, 0xfe, 0xfa, 0x32, 0xf7, 0x7f, 0x82, 0xcd, 0x19, 0x62, - 0x52, 0x6e, 0xea, 0x00, 0x0f, 0x8c, 0x82, 0xf0, 0x8d, 0xf2, 0xef, 0x89, 0x43, 0x9a, 0x2b, 0x29, - 0xaf, 0x4e, 0x19, 0x3e, 0x80, 0x21, 0xfb, 0xf9, 0xbf, 0xc1, 0x2d, 0xc8, 0x62, 0x4b, 0xd6, 0xf8, - 0x44, 0x32, 0xac, 0x5f, 0xfe, 0x07, 0x8b, 0x13, 0xb6, 0x46, 0x5e, 0xef, 0xe6, 0x36, 0x81, 0x32, - 0x67, 0x00, 0x0e, 0xdd, 0x23, 0x63, 0xc9, 0x12, 0x95, 0x97, 0x13, 0xbe, 0x81, 0x73, 0x4b, 0x17, - 0xbb, 0x0c, 0x73, 0x2e, 0x43, 0xf3, 0xa3, 0x51, 0x08, 0x46, 0x92, 0xaf, 0x68, 0x6d, 0xf3, 0xa7, - 0xb1, 0x69, 0xd4, 0x73, 0xc1, 0x89, 0x30, 0x83, 0x51, 0xca, 0x95, 0xd7, 0xb7, 0x6e, 0x43, 0xf0, - 0x5a, 0xe2, 0xa6, 0x93, 0xb2, 0x9f, 0xf6, 0xdf, 0xdd, 0x4a, 0xad, 0x7e, 0xae, 0xd8, 0x52, 0x9f, - 0xc9, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x5c, 0x94, 0x66, 0x73, 0x02, 0x00, 0x00, + // 483 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x41, 0x8f, 0xd3, 0x3c, + 0x14, 0xfc, 0xd2, 0xc4, 0x6e, 0xfa, 0xd2, 0xee, 0xd7, 0x0d, 0x1c, 0x0c, 0x54, 0xa8, 0xb2, 0x84, + 0xb4, 0xa7, 0x1e, 0xd8, 0x0b, 0x82, 0x13, 0x2b, 0x40, 0x14, 0xad, 0x00, 0xc1, 0x09, 0x6e, 0x6e, + 0xfb, 0xb6, 0x1b, 0xda, 0xc4, 0xc1, 0x71, 0x57, 0xea, 0x2f, 0xe2, 0x07, 0xf2, 0x07, 0x78, 0x76, + 0x12, 0xda, 0xa8, 0x08, 0x4e, 0x55, 0xde, 0x8c, 0x67, 0xfc, 0xc6, 0x53, 0x18, 0xea, 0xc5, 0x37, + 0x5c, 0xda, 0x59, 0x69, 0xb4, 0xd5, 0x29, 0xaf, 0x4a, 0x83, 0x6a, 0x25, 0x7f, 0x06, 0xc0, 0xde, + 0x64, 0xb8, 0x5d, 0xa5, 0x09, 0x84, 0x1b, 0xdc, 0x8b, 0x60, 0x1a, 0x5c, 0x0c, 0xd2, 0x31, 0xf0, + 0x62, 0x97, 0x2f, 0xd0, 0x88, 0x1e, 0x7d, 0x07, 0x6f, 0xff, 0x4b, 0x47, 0x10, 0x56, 0xd6, 0x88, + 0xd0, 0xc1, 0xf4, 0x79, 0x0e, 0xfd, 0x85, 0xd6, 0x5b, 0x54, 0x85, 0x88, 0x68, 0x14, 0xd3, 0x68, + 0x0a, 0xbc, 0xb6, 0x10, 0x8c, 0x26, 0xc9, 0xd3, 0xb3, 0x59, 0xed, 0x31, 0xfb, 0xe0, 0xa7, 0xc4, + 0x78, 0x0c, 0x4c, 0x19, 0xa3, 0xf6, 0x82, 0x7b, 0xc2, 0xa8, 0x25, 0xbc, 0x74, 0x43, 0xc2, 0x27, + 0x10, 0x6d, 0xb3, 0x62, 0x23, 0xfa, 0x1e, 0x1e, 0xb6, 0xf0, 0x35, 0xcd, 0xbc, 0x3e, 0x2b, 0x95, + 0x51, 0xb9, 0x88, 0x3d, 0x7c, 0xde, 0xc2, 0x1f, 0xdd, 0x10, 0x2d, 0x1a, 0xd2, 0x8f, 0x94, 0x59, + 0x57, 0x62, 0x30, 0x0d, 0x89, 0x30, 0x3e, 0xc8, 0xaf, 0x77, 0x39, 0x16, 0xf6, 0xaa, 0x0f, 0xec, + 0x4e, 0x6d, 0x77, 0x28, 0x35, 0xf0, 0xfa, 0x52, 0xe9, 0x05, 0xb0, 0xcc, 0x62, 0x5e, 0xd1, 0xde, + 0xee, 0xcc, 0x83, 0xee, 0x9d, 0x67, 0x73, 0x87, 0xbd, 0x2e, 0xac, 0xd9, 0x3f, 0x7c, 0x01, 0x70, + 0xf8, 0xea, 0xa6, 0x35, 0x69, 0x74, 0x7d, 0x58, 0x47, 0x7b, 0xf9, 0x60, 0x9f, 0xf7, 0x9e, 0x05, + 0xf2, 0x09, 0x30, 0xbf, 0xa4, 0xa3, 0x1e, 0xfb, 0x75, 0xa9, 0xf2, 0x12, 0xc2, 0xcf, 0x9f, 0xe6, + 0xe9, 0xff, 0xd0, 0xb7, 0x06, 0x31, 0xab, 0x6e, 0x1b, 0x83, 0x21, 0x44, 0xa5, 0xb2, 0xb7, 0x5e, + 0x7f, 0x40, 0x4f, 0xc1, 0x6e, 0x1c, 0xbd, 0x7e, 0x0c, 0xf9, 0x0e, 0x22, 0x97, 0x50, 0x7a, 0x0f, + 0x92, 0x52, 0x2d, 0x37, 0x6a, 0x8d, 0xef, 0x29, 0x8f, 0xe6, 0xe4, 0x23, 0xe0, 0x96, 0x32, 0x41, + 0xdb, 0xdc, 0x2d, 0x69, 0x0d, 0x9d, 0xcf, 0x18, 0x62, 0x7d, 0x87, 0xc6, 0x64, 0x2b, 0xf4, 0x5a, + 0xb1, 0xfc, 0x02, 0xf1, 0x2b, 0xbd, 0xf4, 0x69, 0x39, 0xd3, 0xe2, 0x20, 0x24, 0x21, 0xca, 0x8a, + 0x1b, 0xdd, 0xc8, 0xdc, 0x6f, 0x65, 0x5a, 0xf6, 0x9c, 0x30, 0x32, 0x8b, 0x8c, 0xd6, 0xd6, 0x6b, + 0x9d, 0xec, 0x36, 0x81, 0x61, 0x87, 0xdc, 0xee, 0xe4, 0xe5, 0xe5, 0x35, 0x0c, 0x0e, 0xef, 0xd8, + 0x75, 0x3e, 0x03, 0x4e, 0x9d, 0xcd, 0x4b, 0xdb, 0xac, 0x4f, 0xe9, 0xd0, 0x41, 0xe2, 0x15, 0x75, + 0x00, 0x6e, 0x0d, 0x83, 0xdf, 0x77, 0x99, 0xc1, 0x55, 0x5d, 0x46, 0xf9, 0x23, 0x80, 0xb8, 0x7d, + 0xf5, 0xa3, 0x2e, 0x07, 0xdd, 0x2e, 0xf7, 0x4e, 0xbb, 0x1c, 0x9e, 0x74, 0x39, 0xfa, 0x57, 0x97, + 0xd9, 0xdf, 0xbb, 0xcc, 0xff, 0xd4, 0xe5, 0xdf, 0x4d, 0xbc, 0x1a, 0x7d, 0x4d, 0x6a, 0xc4, 0xff, + 0x2d, 0x17, 0xdc, 0xff, 0x5c, 0xfe, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x9e, 0x90, 0xe2, 0xad, + 0x03, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto index b0c2a06..80471cf 100644 --- a/proto/object.proto +++ b/proto/object.proto @@ -22,6 +22,8 @@ message Field { Array array = 6; Link link = 7; } + Parameter param = 8; + repeated Argument args = 9; } // Object represents a map with strings for keys. @@ -59,3 +61,23 @@ message Document { message DocumentInfo { string path = 1; } + +// Parameter allows user input to be used in producing the output of the field. +message Parameter { + string name = 1; + string prompt = 2; + string pattern = 3; + bool required = 4; +} + +// Argument contains an argument to fulfill a parameter. +message Argument { + oneof value { + double number = 1; + string str = 2; + bool boolean = 3; + Object object = 4; + Array array = 5; + Link link = 6; + } +} From 907b9afd85605c1d701d7877a80092bf0439998e Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sat, 23 Jul 2016 21:41:33 -0700 Subject: [PATCH 64/79] changed indirect to dereference in simple parameter application --- pkg/data/parameter.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index 642de64..ffa8052 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -41,15 +41,15 @@ func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { func simpleArgApply(field *pb.Field, arg *pb.Argument) error { switch val := arg.GetValue().(type) { case *pb.Argument_Number: - field.Value = *pb.Field_Number{Number: val.Number} + field.Value = &pb.Field_Number{Number: val.Number} case *pb.Argument_Str: - field.Value = *pb.Field_Str{Str: val.Str} + field.Value = &pb.Field_Str{Str: val.Str} case *pb.Argument_Boolean: - field.Value = *pb.Field_Boolean{Boolean: val.Boolean} + field.Value = &pb.Field_Boolean{Boolean: val.Boolean} case *pb.Argument_Object: - field.Value = *pb.Field_Object{Object: val.Object} + field.Value = &pb.Field_Object{Object: val.Object} case *pb.Argument_Array: - field.Value = *pb.Field_Array{Array: val.Array} + field.Value = &pb.Field_Array{Array: val.Array} default: field.Value = nil } From c43386efc682965c2b9c8e815c3c018e5e7d425c Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sat, 23 Jul 2016 21:50:03 -0700 Subject: [PATCH 65/79] removed link from arguments --- pkg/spreadproto/object.pb.go | 94 ++++++++++++------------------------ proto/object.proto | 1 - 2 files changed, 31 insertions(+), 64 deletions(-) diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index d65eb18..876ea9e 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -409,7 +409,6 @@ type Argument struct { // *Argument_Boolean // *Argument_Object // *Argument_Array - // *Argument_Link Value isArgument_Value `protobuf_oneof:"value"` } @@ -437,16 +436,12 @@ type Argument_Object struct { type Argument_Array struct { Array *Array `protobuf:"bytes,5,opt,name=array,oneof"` } -type Argument_Link struct { - Link *Link `protobuf:"bytes,6,opt,name=link,oneof"` -} func (*Argument_Number) isArgument_Value() {} func (*Argument_Str) isArgument_Value() {} func (*Argument_Boolean) isArgument_Value() {} func (*Argument_Object) isArgument_Value() {} func (*Argument_Array) isArgument_Value() {} -func (*Argument_Link) isArgument_Value() {} func (m *Argument) GetValue() isArgument_Value { if m != nil { @@ -490,13 +485,6 @@ func (m *Argument) GetArray() *Array { return nil } -func (m *Argument) GetLink() *Link { - if x, ok := m.GetValue().(*Argument_Link); ok { - return x.Link - } - return nil -} - // XXX_OneofFuncs is for the internal use of the proto package. func (*Argument) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { return _Argument_OneofMarshaler, _Argument_OneofUnmarshaler, _Argument_OneofSizer, []interface{}{ @@ -505,7 +493,6 @@ func (*Argument) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) erro (*Argument_Boolean)(nil), (*Argument_Object)(nil), (*Argument_Array)(nil), - (*Argument_Link)(nil), } } @@ -536,11 +523,6 @@ func _Argument_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { if err := b.EncodeMessage(x.Array); err != nil { return err } - case *Argument_Link: - b.EncodeVarint(6<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Link); err != nil { - return err - } case nil: default: return fmt.Errorf("Argument.Value has unexpected type %T", x) @@ -588,14 +570,6 @@ func _Argument_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffe err := b.DecodeMessage(msg) m.Value = &Argument_Array{msg} return true, err - case 6: // value.link - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(Link) - err := b.DecodeMessage(msg) - m.Value = &Argument_Link{msg} - return true, err default: return false, nil } @@ -625,11 +599,6 @@ func _Argument_OneofSizer(msg proto.Message) (n int) { n += proto.SizeVarint(5<<3 | proto.WireBytes) n += proto.SizeVarint(uint64(s)) n += s - case *Argument_Link: - s := proto.Size(x.Link) - n += proto.SizeVarint(6<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s case nil: default: panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) @@ -650,36 +619,35 @@ func init() { } var fileDescriptor0 = []byte{ - // 483 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x41, 0x8f, 0xd3, 0x3c, - 0x14, 0xfc, 0xd2, 0xc4, 0x6e, 0xfa, 0xd2, 0xee, 0xd7, 0x0d, 0x1c, 0x0c, 0x54, 0xa8, 0xb2, 0x84, - 0xb4, 0xa7, 0x1e, 0xd8, 0x0b, 0x82, 0x13, 0x2b, 0x40, 0x14, 0xad, 0x00, 0xc1, 0x09, 0x6e, 0x6e, - 0xfb, 0xb6, 0x1b, 0xda, 0xc4, 0xc1, 0x71, 0x57, 0xea, 0x2f, 0xe2, 0x07, 0xf2, 0x07, 0x78, 0x76, - 0x12, 0xda, 0xa8, 0x08, 0x4e, 0x55, 0xde, 0x8c, 0x67, 0xfc, 0xc6, 0x53, 0x18, 0xea, 0xc5, 0x37, - 0x5c, 0xda, 0x59, 0x69, 0xb4, 0xd5, 0x29, 0xaf, 0x4a, 0x83, 0x6a, 0x25, 0x7f, 0x06, 0xc0, 0xde, - 0x64, 0xb8, 0x5d, 0xa5, 0x09, 0x84, 0x1b, 0xdc, 0x8b, 0x60, 0x1a, 0x5c, 0x0c, 0xd2, 0x31, 0xf0, - 0x62, 0x97, 0x2f, 0xd0, 0x88, 0x1e, 0x7d, 0x07, 0x6f, 0xff, 0x4b, 0x47, 0x10, 0x56, 0xd6, 0x88, - 0xd0, 0xc1, 0xf4, 0x79, 0x0e, 0xfd, 0x85, 0xd6, 0x5b, 0x54, 0x85, 0x88, 0x68, 0x14, 0xd3, 0x68, - 0x0a, 0xbc, 0xb6, 0x10, 0x8c, 0x26, 0xc9, 0xd3, 0xb3, 0x59, 0xed, 0x31, 0xfb, 0xe0, 0xa7, 0xc4, - 0x78, 0x0c, 0x4c, 0x19, 0xa3, 0xf6, 0x82, 0x7b, 0xc2, 0xa8, 0x25, 0xbc, 0x74, 0x43, 0xc2, 0x27, - 0x10, 0x6d, 0xb3, 0x62, 0x23, 0xfa, 0x1e, 0x1e, 0xb6, 0xf0, 0x35, 0xcd, 0xbc, 0x3e, 0x2b, 0x95, - 0x51, 0xb9, 0x88, 0x3d, 0x7c, 0xde, 0xc2, 0x1f, 0xdd, 0x10, 0x2d, 0x1a, 0xd2, 0x8f, 0x94, 0x59, - 0x57, 0x62, 0x30, 0x0d, 0x89, 0x30, 0x3e, 0xc8, 0xaf, 0x77, 0x39, 0x16, 0xf6, 0xaa, 0x0f, 0xec, - 0x4e, 0x6d, 0x77, 0x28, 0x35, 0xf0, 0xfa, 0x52, 0xe9, 0x05, 0xb0, 0xcc, 0x62, 0x5e, 0xd1, 0xde, - 0xee, 0xcc, 0x83, 0xee, 0x9d, 0x67, 0x73, 0x87, 0xbd, 0x2e, 0xac, 0xd9, 0x3f, 0x7c, 0x01, 0x70, - 0xf8, 0xea, 0xa6, 0x35, 0x69, 0x74, 0x7d, 0x58, 0x47, 0x7b, 0xf9, 0x60, 0x9f, 0xf7, 0x9e, 0x05, - 0xf2, 0x09, 0x30, 0xbf, 0xa4, 0xa3, 0x1e, 0xfb, 0x75, 0xa9, 0xf2, 0x12, 0xc2, 0xcf, 0x9f, 0xe6, - 0xe9, 0xff, 0xd0, 0xb7, 0x06, 0x31, 0xab, 0x6e, 0x1b, 0x83, 0x21, 0x44, 0xa5, 0xb2, 0xb7, 0x5e, - 0x7f, 0x40, 0x4f, 0xc1, 0x6e, 0x1c, 0xbd, 0x7e, 0x0c, 0xf9, 0x0e, 0x22, 0x97, 0x50, 0x7a, 0x0f, - 0x92, 0x52, 0x2d, 0x37, 0x6a, 0x8d, 0xef, 0x29, 0x8f, 0xe6, 0xe4, 0x23, 0xe0, 0x96, 0x32, 0x41, - 0xdb, 0xdc, 0x2d, 0x69, 0x0d, 0x9d, 0xcf, 0x18, 0x62, 0x7d, 0x87, 0xc6, 0x64, 0x2b, 0xf4, 0x5a, - 0xb1, 0xfc, 0x02, 0xf1, 0x2b, 0xbd, 0xf4, 0x69, 0x39, 0xd3, 0xe2, 0x20, 0x24, 0x21, 0xca, 0x8a, - 0x1b, 0xdd, 0xc8, 0xdc, 0x6f, 0x65, 0x5a, 0xf6, 0x9c, 0x30, 0x32, 0x8b, 0x8c, 0xd6, 0xd6, 0x6b, - 0x9d, 0xec, 0x36, 0x81, 0x61, 0x87, 0xdc, 0xee, 0xe4, 0xe5, 0xe5, 0x35, 0x0c, 0x0e, 0xef, 0xd8, - 0x75, 0x3e, 0x03, 0x4e, 0x9d, 0xcd, 0x4b, 0xdb, 0xac, 0x4f, 0xe9, 0xd0, 0x41, 0xe2, 0x15, 0x75, - 0x00, 0x6e, 0x0d, 0x83, 0xdf, 0x77, 0x99, 0xc1, 0x55, 0x5d, 0x46, 0xf9, 0x23, 0x80, 0xb8, 0x7d, - 0xf5, 0xa3, 0x2e, 0x07, 0xdd, 0x2e, 0xf7, 0x4e, 0xbb, 0x1c, 0x9e, 0x74, 0x39, 0xfa, 0x57, 0x97, - 0xd9, 0xdf, 0xbb, 0xcc, 0xff, 0xd4, 0xe5, 0xdf, 0x4d, 0xbc, 0x1a, 0x7d, 0x4d, 0x6a, 0xc4, 0xff, - 0x2d, 0x17, 0xdc, 0xff, 0x5c, 0xfe, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x9e, 0x90, 0xe2, 0xad, - 0x03, 0x00, 0x00, + // 478 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0xc1, 0x8e, 0xd3, 0x30, + 0x14, 0x24, 0x4d, 0x9c, 0xa6, 0x2f, 0xed, 0xd2, 0x0d, 0x1c, 0x0c, 0x54, 0xa8, 0xb2, 0x84, 0xb4, + 0xa7, 0x1e, 0xd8, 0x0b, 0x82, 0x13, 0x2b, 0x40, 0x14, 0xad, 0x00, 0xc1, 0x09, 0x6e, 0x6e, 0xfb, + 0xb6, 0x1b, 0xda, 0xc4, 0xc1, 0x71, 0x57, 0xea, 0x27, 0xf0, 0xbd, 0xfc, 0x00, 0xcf, 0x4e, 0x4c, + 0x1b, 0x15, 0x89, 0x53, 0x95, 0x37, 0xe3, 0x19, 0xbf, 0xf1, 0x14, 0x86, 0x6a, 0xf1, 0x03, 0x97, + 0x66, 0x56, 0x69, 0x65, 0x54, 0x16, 0xd7, 0x95, 0x46, 0xb9, 0x12, 0xbf, 0x03, 0x60, 0xef, 0x72, + 0xdc, 0xae, 0xb2, 0x14, 0xc2, 0x0d, 0xee, 0x79, 0x30, 0x0d, 0x2e, 0x06, 0xd9, 0x18, 0xe2, 0x72, + 0x57, 0x2c, 0x50, 0xf3, 0x1e, 0x7d, 0x07, 0xef, 0xef, 0x65, 0x23, 0x08, 0x6b, 0xa3, 0x79, 0x68, + 0x61, 0xfa, 0x3c, 0x87, 0xfe, 0x42, 0xa9, 0x2d, 0xca, 0x92, 0x47, 0x34, 0x4a, 0x68, 0x34, 0x85, + 0xb8, 0xb1, 0xe0, 0x8c, 0x26, 0xe9, 0xf3, 0xb3, 0x59, 0xe3, 0x31, 0xfb, 0xe4, 0xa6, 0xc4, 0x78, + 0x0a, 0x4c, 0x6a, 0x2d, 0xf7, 0x3c, 0x76, 0x84, 0x91, 0x27, 0xbc, 0xb6, 0x43, 0xc2, 0x27, 0x10, + 0x6d, 0xf3, 0x72, 0xc3, 0xfb, 0x0e, 0x1e, 0x7a, 0xf8, 0x9a, 0x66, 0x4e, 0x9f, 0x55, 0x52, 0xcb, + 0x82, 0x27, 0x0e, 0x3e, 0xf7, 0xf0, 0x67, 0x3b, 0x44, 0x83, 0x9a, 0xf4, 0x23, 0xa9, 0xd7, 0x35, + 0x1f, 0x4c, 0x43, 0x22, 0x8c, 0x0f, 0xf2, 0xeb, 0x5d, 0x81, 0xa5, 0xb9, 0xea, 0x03, 0xbb, 0x93, + 0xdb, 0x1d, 0x0a, 0x05, 0x71, 0x73, 0xa9, 0xec, 0x02, 0x58, 0x6e, 0xb0, 0xa8, 0x69, 0x6f, 0x7b, + 0xe6, 0x51, 0xf7, 0xce, 0xb3, 0xb9, 0xc5, 0xde, 0x96, 0x46, 0xef, 0x1f, 0xbf, 0x02, 0x38, 0x7c, + 0x75, 0xd3, 0x9a, 0xb4, 0xba, 0x2e, 0xac, 0xa3, 0xbd, 0x5c, 0xb0, 0x2f, 0x7b, 0x2f, 0x02, 0xf1, + 0x0c, 0x98, 0x5b, 0xd2, 0x52, 0x8f, 0xfd, 0xba, 0x54, 0x71, 0x09, 0xe1, 0xd7, 0x2f, 0xf3, 0xec, + 0x3e, 0xf4, 0x8d, 0x46, 0xcc, 0xeb, 0xdb, 0xd6, 0x60, 0x08, 0x51, 0x25, 0xcd, 0xad, 0xd3, 0x1f, + 0xd0, 0x53, 0xb0, 0x1b, 0x4b, 0x6f, 0x1e, 0x43, 0x7c, 0x80, 0xc8, 0x26, 0x94, 0x3d, 0x80, 0xb4, + 0x92, 0xcb, 0x8d, 0x5c, 0xe3, 0x47, 0xca, 0xa3, 0x3d, 0xf9, 0x04, 0x62, 0x43, 0x99, 0xa0, 0x69, + 0xef, 0x96, 0x7a, 0x43, 0xeb, 0x33, 0x86, 0x44, 0xdd, 0xa1, 0xd6, 0xf9, 0x0a, 0x9d, 0x56, 0x22, + 0xbe, 0x41, 0xf2, 0x46, 0x2d, 0x5d, 0x5a, 0xd6, 0xb4, 0x3c, 0x08, 0x09, 0x88, 0xf2, 0xf2, 0x46, + 0xb5, 0x32, 0x0f, 0xbd, 0x8c, 0x67, 0xcf, 0x09, 0x23, 0xb3, 0x48, 0x2b, 0x65, 0x9c, 0xd6, 0xc9, + 0x6e, 0x13, 0x18, 0x76, 0xc8, 0x7e, 0x27, 0x27, 0x2f, 0xae, 0x61, 0x70, 0x78, 0xc7, 0xae, 0xf3, + 0x19, 0xc4, 0xd4, 0xd9, 0xa2, 0x32, 0xed, 0xfa, 0x94, 0x0e, 0x1d, 0x24, 0x5e, 0xd9, 0x04, 0x60, + 0xd7, 0xd0, 0xf8, 0x73, 0x97, 0x6b, 0x5c, 0x35, 0x65, 0x14, 0xbf, 0x02, 0x48, 0xfc, 0xab, 0x1f, + 0x75, 0x39, 0xe8, 0x76, 0xb9, 0x77, 0xda, 0xe5, 0xf0, 0xa4, 0xcb, 0xd1, 0xff, 0xba, 0xcc, 0xfe, + 0xd9, 0xe5, 0xbf, 0x5d, 0xbb, 0x1a, 0x7d, 0x4f, 0x1b, 0xc8, 0xfd, 0xf1, 0x16, 0xb1, 0xfb, 0xb9, + 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0x96, 0xb6, 0x8b, 0xec, 0x8f, 0x03, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto index 80471cf..e61a001 100644 --- a/proto/object.proto +++ b/proto/object.proto @@ -78,6 +78,5 @@ message Argument { bool boolean = 3; Object object = 4; Array array = 5; - Link link = 6; } } From 7d3b5dc2df92cead57f37dd7a15e3d9adc16c90b Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sun, 24 Jul 2016 11:24:07 -0700 Subject: [PATCH 66/79] added initial argument tests --- pkg/data/document_test.go | 74 +++++++++++++++++++++ pkg/data/parameter.go | 26 ++++++-- pkg/data/parameter_test.go | 53 +++++++++++++++ pkg/spreadproto/object.pb.go | 124 +++++++++-------------------------- proto/object.proto | 2 - 5 files changed, 178 insertions(+), 101 deletions(-) create mode 100644 pkg/data/document_test.go create mode 100644 pkg/data/parameter_test.go diff --git a/pkg/data/document_test.go b/pkg/data/document_test.go new file mode 100644 index 0000000..d528990 --- /dev/null +++ b/pkg/data/document_test.go @@ -0,0 +1,74 @@ +package data + +import ( + "math/rand" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +func randomField() *pb.Field { + key := randomString(rand.Intn(9)) + switch rand.Intn(5) { + case 0: // number + return &pb.Field{ + Key: key, + Value: &pb.Field_Number{Number: rand.Float64()}, + } + case 1: // string + return &pb.Field{ + Key: key, + Value: &pb.Field_Str{Str: randomString(40)}, + } + case 2: // boolean + b := rand.Intn(2) == 0 + return &pb.Field{ + Key: key, + Value: &pb.Field_Boolean{Boolean: b}, + } + case 3: // object + return &pb.Field{ + Key: key, + Value: &pb.Field_Object{Object: randomObject()}, + } + case 4: // array + return &pb.Field{ + Key: key, + Value: &pb.Field_Array{Array: randomArray()}, + } + default: + panic("illegal option") + } +} + +func randomObject() *pb.Object { + numFields := rand.Intn(20) + fields := make(map[string]*pb.Field, numFields) + for i := 0; i < numFields; i++ { + field := randomField() + fields[field.Key] = field + } + return &pb.Object{ + Items: fields, + } +} + +func randomArray() *pb.Array { + numFields := rand.Intn(20) + fields := make([]*pb.Field, numFields) + for i := 0; i < numFields; i++ { + field := randomField() + fields[i] = field + } + return &pb.Array{ + Items: fields, + } +} + +func randomString(strlen int) string { + const chars = "abcdefghijklmnopqrstuvwxyz0123456789" + result := make([]byte, strlen) + for i := 0; i < strlen; i++ { + result[i] = chars[rand.Intn(len(chars))] + } + return string(result) +} diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index ffa8052..d584d58 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -22,7 +22,9 @@ func AddParamToDoc(doc *pb.Document, target *SRI, param *pb.Parameter) error { return nil } -// ApplyArguments takes the given arguments and uses them to satisfy a field parameter. +// ApplyArguments takes the given arguments and uses them to satisfy a field parameter. If a single argument and no +// formatting pattern are given the single argument is used as the field value. Otherwise the arguments will be used as +// arguments to Printf with the formatting string as the pattern. func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { if field == nil { return errors.New("field was nil") @@ -32,8 +34,24 @@ func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { return errors.New("an argument must be specified") } else if len(args) == 1 && len(field.GetParam().Pattern) == 0 { return simpleArgApply(field, args[0]) + } else if len(args) > 1 && len(field.GetParam().Pattern) == 0 { + return errors.New("may only use multiple arguments if a string template is provided") } - // TODO: complete string formatting based apply + + argVals := make([]interface{}, len(args)) + for i, v := range args { + switch val := v.GetValue().(type) { + case *pb.Argument_Number: + argVals[i] = val.Number + case *pb.Argument_Str: + argVals[i] = val.Str + case *pb.Argument_Boolean: + argVals[i] = val.Boolean + } + } + + val := fmt.Sprintf(field.GetParam().Pattern, argVals...) + field.Value = &pb.Field_Str{Str: val} return nil } @@ -46,10 +64,6 @@ func simpleArgApply(field *pb.Field, arg *pb.Argument) error { field.Value = &pb.Field_Str{Str: val.Str} case *pb.Argument_Boolean: field.Value = &pb.Field_Boolean{Boolean: val.Boolean} - case *pb.Argument_Object: - field.Value = &pb.Field_Object{Object: val.Object} - case *pb.Argument_Array: - field.Value = &pb.Field_Array{Array: val.Array} default: field.Value = nil } diff --git a/pkg/data/parameter_test.go b/pkg/data/parameter_test.go new file mode 100644 index 0000000..744dcbb --- /dev/null +++ b/pkg/data/parameter_test.go @@ -0,0 +1,53 @@ +package data + +import ( + "testing" + + pb "rsprd.com/spread/pkg/spreadproto" +) + +type ArgTest struct { + In *pb.Field + Args []*pb.Argument + Out *pb.Field + Error bool +} + +var argTests = []ArgTest{ + { // nil field + In: nil, + Error: true, + }, + { // no args + In: randomField(), + Args: []*pb.Argument{}, + Error: true, + }, + { // no parameter + In: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "original"}, + }, + Args: []*pb.Argument{ + {Value: &pb.Argument_Str{Str: "test"}}, + {Value: &pb.Argument_Boolean{Boolean: true}}, + }, + Error: true, + }, +} + +func TestApplyArguments(t *testing.T) { + for i, test := range argTests { + err := ApplyArguments(test.In, test.Args...) + hasErr := err != nil + if hasErr != test.Error { + if test.Error { + t.Errorf("test %d: should have returned error", i) + } else { + t.Errorf("test %d: shouldn't have errored. Error: %v", i, err) + } + } + + // TODO: check that value was correctly found + } +} diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index 876ea9e..57c8bda 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -407,8 +407,6 @@ type Argument struct { // *Argument_Number // *Argument_Str // *Argument_Boolean - // *Argument_Object - // *Argument_Array Value isArgument_Value `protobuf_oneof:"value"` } @@ -430,18 +428,10 @@ type Argument_Str struct { type Argument_Boolean struct { Boolean bool `protobuf:"varint,3,opt,name=boolean,oneof"` } -type Argument_Object struct { - Object *Object `protobuf:"bytes,4,opt,name=object,oneof"` -} -type Argument_Array struct { - Array *Array `protobuf:"bytes,5,opt,name=array,oneof"` -} func (*Argument_Number) isArgument_Value() {} func (*Argument_Str) isArgument_Value() {} func (*Argument_Boolean) isArgument_Value() {} -func (*Argument_Object) isArgument_Value() {} -func (*Argument_Array) isArgument_Value() {} func (m *Argument) GetValue() isArgument_Value { if m != nil { @@ -471,28 +461,12 @@ func (m *Argument) GetBoolean() bool { return false } -func (m *Argument) GetObject() *Object { - if x, ok := m.GetValue().(*Argument_Object); ok { - return x.Object - } - return nil -} - -func (m *Argument) GetArray() *Array { - if x, ok := m.GetValue().(*Argument_Array); ok { - return x.Array - } - return nil -} - // XXX_OneofFuncs is for the internal use of the proto package. func (*Argument) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { return _Argument_OneofMarshaler, _Argument_OneofUnmarshaler, _Argument_OneofSizer, []interface{}{ (*Argument_Number)(nil), (*Argument_Str)(nil), (*Argument_Boolean)(nil), - (*Argument_Object)(nil), - (*Argument_Array)(nil), } } @@ -513,16 +487,6 @@ func _Argument_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { } b.EncodeVarint(3<<3 | proto.WireVarint) b.EncodeVarint(t) - case *Argument_Object: - b.EncodeVarint(4<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Object); err != nil { - return err - } - case *Argument_Array: - b.EncodeVarint(5<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Array); err != nil { - return err - } case nil: default: return fmt.Errorf("Argument.Value has unexpected type %T", x) @@ -554,22 +518,6 @@ func _Argument_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffe x, err := b.DecodeVarint() m.Value = &Argument_Boolean{x != 0} return true, err - case 4: // value.object - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(Object) - err := b.DecodeMessage(msg) - m.Value = &Argument_Object{msg} - return true, err - case 5: // value.array - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(Array) - err := b.DecodeMessage(msg) - m.Value = &Argument_Array{msg} - return true, err default: return false, nil } @@ -589,16 +537,6 @@ func _Argument_OneofSizer(msg proto.Message) (n int) { case *Argument_Boolean: n += proto.SizeVarint(3<<3 | proto.WireVarint) n += 1 - case *Argument_Object: - s := proto.Size(x.Object) - n += proto.SizeVarint(4<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case *Argument_Array: - s := proto.Size(x.Array) - n += proto.SizeVarint(5<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s case nil: default: panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) @@ -619,35 +557,35 @@ func init() { } var fileDescriptor0 = []byte{ - // 478 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0xc1, 0x8e, 0xd3, 0x30, - 0x14, 0x24, 0x4d, 0x9c, 0xa6, 0x2f, 0xed, 0xd2, 0x0d, 0x1c, 0x0c, 0x54, 0xa8, 0xb2, 0x84, 0xb4, - 0xa7, 0x1e, 0xd8, 0x0b, 0x82, 0x13, 0x2b, 0x40, 0x14, 0xad, 0x00, 0xc1, 0x09, 0x6e, 0x6e, 0xfb, - 0xb6, 0x1b, 0xda, 0xc4, 0xc1, 0x71, 0x57, 0xea, 0x27, 0xf0, 0xbd, 0xfc, 0x00, 0xcf, 0x4e, 0x4c, - 0x1b, 0x15, 0x89, 0x53, 0x95, 0x37, 0xe3, 0x19, 0xbf, 0xf1, 0x14, 0x86, 0x6a, 0xf1, 0x03, 0x97, - 0x66, 0x56, 0x69, 0x65, 0x54, 0x16, 0xd7, 0x95, 0x46, 0xb9, 0x12, 0xbf, 0x03, 0x60, 0xef, 0x72, - 0xdc, 0xae, 0xb2, 0x14, 0xc2, 0x0d, 0xee, 0x79, 0x30, 0x0d, 0x2e, 0x06, 0xd9, 0x18, 0xe2, 0x72, - 0x57, 0x2c, 0x50, 0xf3, 0x1e, 0x7d, 0x07, 0xef, 0xef, 0x65, 0x23, 0x08, 0x6b, 0xa3, 0x79, 0x68, - 0x61, 0xfa, 0x3c, 0x87, 0xfe, 0x42, 0xa9, 0x2d, 0xca, 0x92, 0x47, 0x34, 0x4a, 0x68, 0x34, 0x85, - 0xb8, 0xb1, 0xe0, 0x8c, 0x26, 0xe9, 0xf3, 0xb3, 0x59, 0xe3, 0x31, 0xfb, 0xe4, 0xa6, 0xc4, 0x78, - 0x0a, 0x4c, 0x6a, 0x2d, 0xf7, 0x3c, 0x76, 0x84, 0x91, 0x27, 0xbc, 0xb6, 0x43, 0xc2, 0x27, 0x10, - 0x6d, 0xf3, 0x72, 0xc3, 0xfb, 0x0e, 0x1e, 0x7a, 0xf8, 0x9a, 0x66, 0x4e, 0x9f, 0x55, 0x52, 0xcb, - 0x82, 0x27, 0x0e, 0x3e, 0xf7, 0xf0, 0x67, 0x3b, 0x44, 0x83, 0x9a, 0xf4, 0x23, 0xa9, 0xd7, 0x35, - 0x1f, 0x4c, 0x43, 0x22, 0x8c, 0x0f, 0xf2, 0xeb, 0x5d, 0x81, 0xa5, 0xb9, 0xea, 0x03, 0xbb, 0x93, - 0xdb, 0x1d, 0x0a, 0x05, 0x71, 0x73, 0xa9, 0xec, 0x02, 0x58, 0x6e, 0xb0, 0xa8, 0x69, 0x6f, 0x7b, - 0xe6, 0x51, 0xf7, 0xce, 0xb3, 0xb9, 0xc5, 0xde, 0x96, 0x46, 0xef, 0x1f, 0xbf, 0x02, 0x38, 0x7c, - 0x75, 0xd3, 0x9a, 0xb4, 0xba, 0x2e, 0xac, 0xa3, 0xbd, 0x5c, 0xb0, 0x2f, 0x7b, 0x2f, 0x02, 0xf1, - 0x0c, 0x98, 0x5b, 0xd2, 0x52, 0x8f, 0xfd, 0xba, 0x54, 0x71, 0x09, 0xe1, 0xd7, 0x2f, 0xf3, 0xec, - 0x3e, 0xf4, 0x8d, 0x46, 0xcc, 0xeb, 0xdb, 0xd6, 0x60, 0x08, 0x51, 0x25, 0xcd, 0xad, 0xd3, 0x1f, - 0xd0, 0x53, 0xb0, 0x1b, 0x4b, 0x6f, 0x1e, 0x43, 0x7c, 0x80, 0xc8, 0x26, 0x94, 0x3d, 0x80, 0xb4, - 0x92, 0xcb, 0x8d, 0x5c, 0xe3, 0x47, 0xca, 0xa3, 0x3d, 0xf9, 0x04, 0x62, 0x43, 0x99, 0xa0, 0x69, - 0xef, 0x96, 0x7a, 0x43, 0xeb, 0x33, 0x86, 0x44, 0xdd, 0xa1, 0xd6, 0xf9, 0x0a, 0x9d, 0x56, 0x22, - 0xbe, 0x41, 0xf2, 0x46, 0x2d, 0x5d, 0x5a, 0xd6, 0xb4, 0x3c, 0x08, 0x09, 0x88, 0xf2, 0xf2, 0x46, - 0xb5, 0x32, 0x0f, 0xbd, 0x8c, 0x67, 0xcf, 0x09, 0x23, 0xb3, 0x48, 0x2b, 0x65, 0x9c, 0xd6, 0xc9, - 0x6e, 0x13, 0x18, 0x76, 0xc8, 0x7e, 0x27, 0x27, 0x2f, 0xae, 0x61, 0x70, 0x78, 0xc7, 0xae, 0xf3, - 0x19, 0xc4, 0xd4, 0xd9, 0xa2, 0x32, 0xed, 0xfa, 0x94, 0x0e, 0x1d, 0x24, 0x5e, 0xd9, 0x04, 0x60, - 0xd7, 0xd0, 0xf8, 0x73, 0x97, 0x6b, 0x5c, 0x35, 0x65, 0x14, 0xbf, 0x02, 0x48, 0xfc, 0xab, 0x1f, - 0x75, 0x39, 0xe8, 0x76, 0xb9, 0x77, 0xda, 0xe5, 0xf0, 0xa4, 0xcb, 0xd1, 0xff, 0xba, 0xcc, 0xfe, - 0xd9, 0xe5, 0xbf, 0x5d, 0xbb, 0x1a, 0x7d, 0x4f, 0x1b, 0xc8, 0xfd, 0xf1, 0x16, 0xb1, 0xfb, 0xb9, - 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0x96, 0xb6, 0x8b, 0xec, 0x8f, 0x03, 0x00, 0x00, + // 468 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xc5, 0xf1, 0x47, 0x9c, 0x71, 0x52, 0x52, 0xc3, 0x61, 0x81, 0x08, 0x45, 0x2b, 0x21, 0xf5, + 0x94, 0x03, 0xbd, 0x20, 0x38, 0x51, 0xf1, 0x15, 0x54, 0x01, 0x82, 0x13, 0xdc, 0x36, 0xc9, 0x34, + 0x35, 0x89, 0xbd, 0x66, 0xbc, 0xa9, 0x94, 0xdf, 0xcd, 0x1f, 0x60, 0x76, 0x6d, 0xe3, 0x58, 0x3d, + 0x59, 0x3b, 0xf3, 0xf6, 0xbd, 0x7d, 0x6f, 0xc6, 0x30, 0xd6, 0xab, 0xdf, 0xb8, 0x36, 0x8b, 0x92, + 0xb4, 0xd1, 0x69, 0x54, 0x95, 0x84, 0x6a, 0x23, 0xff, 0x7a, 0x10, 0x7e, 0xc8, 0x70, 0xbf, 0x49, + 0x13, 0xf0, 0x77, 0x78, 0x14, 0xde, 0xdc, 0xbb, 0x18, 0xa5, 0x53, 0x88, 0x8a, 0x43, 0xbe, 0x42, + 0x12, 0x03, 0x3e, 0x7b, 0x9f, 0x1e, 0xa4, 0x13, 0xf0, 0x2b, 0x43, 0xc2, 0xb7, 0x6d, 0x3e, 0x9e, + 0xc3, 0x70, 0xa5, 0xf5, 0x1e, 0x55, 0x21, 0x02, 0x2e, 0xc5, 0x5c, 0x9a, 0x43, 0x54, 0x4b, 0x88, + 0x90, 0x2b, 0xc9, 0xcb, 0xb3, 0x45, 0xad, 0xb1, 0xf8, 0xea, 0xaa, 0x8c, 0x78, 0x0e, 0xa1, 0x22, + 0x52, 0x47, 0x11, 0x39, 0xc0, 0xa4, 0x05, 0xbc, 0xb5, 0x45, 0xee, 0xcf, 0x20, 0xd8, 0x67, 0xc5, + 0x4e, 0x0c, 0x5d, 0x7b, 0xdc, 0xb6, 0xaf, 0xb9, 0xe6, 0xf8, 0xc3, 0x52, 0x91, 0xca, 0x45, 0xec, + 0xda, 0xe7, 0x6d, 0xfb, 0x9b, 0x2d, 0xa2, 0x41, 0x62, 0xfe, 0x40, 0xd1, 0xb6, 0x12, 0xa3, 0xb9, + 0xcf, 0x80, 0x69, 0x47, 0xbf, 0x3d, 0xe4, 0x58, 0x98, 0xab, 0x21, 0x84, 0x77, 0x6a, 0x7f, 0x40, + 0xa9, 0x21, 0xaa, 0x1f, 0x95, 0x5e, 0x40, 0x98, 0x19, 0xcc, 0x2b, 0xf6, 0x6d, 0xef, 0x3c, 0xe9, + 0xbf, 0x79, 0xb1, 0xb4, 0xbd, 0xf7, 0x85, 0xa1, 0xe3, 0xd3, 0x37, 0x00, 0xdd, 0xa9, 0x9f, 0xd6, + 0xac, 0xe1, 0x75, 0x61, 0x9d, 0xf8, 0x72, 0xc1, 0xbe, 0x1e, 0xbc, 0xf2, 0xe4, 0x0b, 0x08, 0x9d, + 0x49, 0x0b, 0x3d, 0xd5, 0xeb, 0x43, 0xe5, 0x25, 0xf8, 0x3f, 0xbe, 0x2f, 0xd3, 0x87, 0x30, 0x34, + 0x84, 0x98, 0x55, 0xb7, 0x8d, 0xc0, 0x18, 0x82, 0x52, 0x99, 0x5b, 0xc7, 0x3f, 0xe2, 0x51, 0x84, + 0x37, 0x16, 0x5e, 0x0f, 0x43, 0x7e, 0x86, 0xc0, 0x26, 0x94, 0x3e, 0x82, 0xa4, 0x54, 0xeb, 0x9d, + 0xda, 0xe2, 0x17, 0xce, 0xa3, 0xb9, 0xf9, 0x0c, 0x22, 0xc3, 0x99, 0xa0, 0x69, 0xde, 0x96, 0xb4, + 0x82, 0x56, 0x67, 0x0a, 0xb1, 0xbe, 0x43, 0xa2, 0x6c, 0x83, 0x8e, 0x2b, 0x96, 0x3f, 0x21, 0x7e, + 0xa7, 0xd7, 0x2e, 0x2d, 0x2b, 0x5a, 0x74, 0x44, 0x12, 0x82, 0xac, 0xb8, 0xd1, 0x0d, 0xcd, 0xe3, + 0x96, 0xa6, 0x45, 0x2f, 0xb9, 0xc7, 0x62, 0x01, 0x69, 0x6d, 0x1c, 0xd7, 0x3d, 0x6f, 0x33, 0x18, + 0xf7, 0xc0, 0xad, 0x27, 0x47, 0x2f, 0xaf, 0x61, 0xd4, 0xcd, 0xb1, 0xaf, 0x7c, 0x06, 0x11, 0xef, + 0x6c, 0x5e, 0x9a, 0xc6, 0x3e, 0xa7, 0xc3, 0x17, 0x19, 0x57, 0xd4, 0x01, 0x58, 0x1b, 0x84, 0x7f, + 0x0e, 0x19, 0xe1, 0xa6, 0x5e, 0x46, 0xf9, 0x11, 0xe2, 0x76, 0xe8, 0x27, 0xab, 0xec, 0xf5, 0x57, + 0x79, 0x70, 0x7f, 0x95, 0xfd, 0x7a, 0x95, 0xff, 0x2f, 0xca, 0xd5, 0xe4, 0x57, 0x52, 0x9b, 0x70, + 0x7f, 0xcd, 0x2a, 0x72, 0x9f, 0xcb, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xf6, 0xe0, 0xed, + 0x4c, 0x03, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto index e61a001..1d661b3 100644 --- a/proto/object.proto +++ b/proto/object.proto @@ -76,7 +76,5 @@ message Argument { double number = 1; string str = 2; bool boolean = 3; - Object object = 4; - Array array = 5; } } From 5114b20075be396ab99ea9b56bbe1264012676d2 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sun, 24 Jul 2016 12:36:50 -0700 Subject: [PATCH 67/79] changed required param to nillable default, added more arg/param tests --- pkg/data/fields.go | 72 ++++++++++++++++++++++++++++ pkg/data/parameter.go | 24 +++++++++- pkg/data/parameter_test.go | 91 ++++++++++++++++++++++++++++++++---- pkg/spreadproto/object.pb.go | 78 +++++++++++++++++-------------- proto/object.proto | 3 +- 5 files changed, 222 insertions(+), 46 deletions(-) diff --git a/pkg/data/fields.go b/pkg/data/fields.go index 2acd61b..7333e0a 100644 --- a/pkg/data/fields.go +++ b/pkg/data/fields.go @@ -24,6 +24,78 @@ func ResolveRelativeField(field *pb.Field, fieldpath string) (resolvedField *pb. return ResolveRelativeField(resolvedField, next) } +// FieldValueEquals returns true if the value of the given fields is the same. +func FieldValueEquals(this, other *pb.Field) bool { + // check for pointer + primitive matches and nil values + switch { + case this == other: + return true + case this == nil || other == nil: + return false + case this.GetValue() == other.GetValue(): + return true + case this.GetValue() == nil || other.GetValue() == nil: + return false + } + + // check for matches with objects and arrays + switch val := this.GetValue().(type) { + case *pb.Field_Number: + otherVal, ok := other.GetValue().(*pb.Field_Number) + if !ok { + return false + } + return val.Number == otherVal.Number + case *pb.Field_Str: + otherVal, ok := other.GetValue().(*pb.Field_Str) + if !ok { + return false + } + return val.Str == otherVal.Str + case *pb.Field_Boolean: + otherVal, ok := other.GetValue().(*pb.Field_Boolean) + if !ok { + return false + } + return val.Boolean == otherVal.Boolean + case *pb.Field_Object: + otherVal, ok := other.GetValue().(*pb.Field_Object) + if !ok { + return false + } + items, otherItems := val.Object.GetItems(), otherVal.Object.GetItems() + + if len(items) != len(otherItems) { + return false + } + + for k, v := range items { + otherV, ok := otherItems[k] + if !ok { + return false + } + + return FieldValueEquals(v, otherV) + } + case *pb.Field_Array: + otherVal, ok := other.GetValue().(*pb.Field_Array) + if !ok { + return false + } + items, otherItems := val.Array.GetItems(), otherVal.Array.GetItems() + + if len(items) != len(otherItems) { + return false + } + + for k, v := range items { + return FieldValueEquals(v, otherItems[k]) + } + } + + return false +} + func getFromArrayField(field *pb.Field, index int) (*pb.Field, error) { fieldArr := field.GetArray() if fieldArr == nil { diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index d584d58..747deef 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -30,8 +30,10 @@ func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { return errors.New("field was nil") } else if field.GetParam() == nil { return fmt.Errorf("field %s does not have a parameter", field.Key) + } else if len(args) < 1 && field.GetParam().GetDefault() == nil { + return errors.New("an argument must be specified if no default is given") } else if len(args) < 1 { - return errors.New("an argument must be specified") + return applyDefault(field) } else if len(args) == 1 && len(field.GetParam().Pattern) == 0 { return simpleArgApply(field, args[0]) } else if len(args) > 1 && len(field.GetParam().Pattern) == 0 { @@ -70,3 +72,23 @@ func simpleArgApply(field *pb.Field, arg *pb.Argument) error { return nil } + +func applyDefault(field *pb.Field) error { + if field == nil { + return errors.New("field cannot be nil") + } else if field.GetParam() == nil { + return errors.New("field does not have parameters") + } else if field.GetParam().GetDefault() == nil { + return errors.New("fields has paramaters but default was nil") + } + + switch d := field.GetParam().GetDefault().GetValue().(type) { + case *pb.Argument_Number: + field.Value = &pb.Field_Number{Number: d.Number} + case *pb.Argument_Str: + field.Value = &pb.Field_Str{Str: d.Str} + case *pb.Argument_Boolean: + field.Value = &pb.Field_Boolean{Boolean: d.Boolean} + } + return nil +} diff --git a/pkg/data/parameter_test.go b/pkg/data/parameter_test.go index 744dcbb..89ad4f6 100644 --- a/pkg/data/parameter_test.go +++ b/pkg/data/parameter_test.go @@ -34,20 +34,93 @@ var argTests = []ArgTest{ }, Error: true, }, + { // default value + In: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "original"}, + Param: &pb.Parameter{ + Default: &pb.Argument{ + Value: &pb.Argument_Str{Str: "haldo"}, + }, + }, + }, + Args: []*pb.Argument{}, + Out: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "haldo"}, + }, + }, + { // simple sub + In: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "original"}, + Param: &pb.Parameter{ + Default: &pb.Argument{ + Value: &pb.Argument_Str{Str: "haldo"}, + }, + }, + }, + Args: []*pb.Argument{ + { + Value: &pb.Argument_Number{Number: 3.34334}, + }, + }, + Out: &pb.Field{ + Key: "testField", + Value: &pb.Field_Number{Number: 3.34334}, + }, + }, + { // sub -- too many args + In: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "original"}, + Param: &pb.Parameter{ + Default: &pb.Argument{ + Value: &pb.Argument_Str{Str: "haldo"}, + }, + }, + }, + Args: []*pb.Argument{ + { + Value: &pb.Argument_Number{Number: 3.34334}, + }, + { + Value: &pb.Argument_Str{Str: "HELO"}, + }, + }, + Error: true, + }, + { // formatted string + In: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "original"}, + Param: &pb.Parameter{ + Pattern: "we are %s", + }, + }, + Args: []*pb.Argument{ + { + Value: &pb.Argument_Str{Str: "maryland"}, + }, + }, + Out: &pb.Field{ + Key: "testField", + Value: &pb.Field_Str{Str: "we are maryland"}, + }, + }, } func TestApplyArguments(t *testing.T) { for i, test := range argTests { - err := ApplyArguments(test.In, test.Args...) + field := test.In + err := ApplyArguments(field, test.Args...) hasErr := err != nil - if hasErr != test.Error { - if test.Error { - t.Errorf("test %d: should have returned error", i) - } else { - t.Errorf("test %d: shouldn't have errored. Error: %v", i, err) - } + if !hasErr && test.Error { + t.Errorf("test %d: should have returned error", i) + } else if hasErr && !test.Error { + t.Errorf("test %d: shouldn't have errored. Error: %v", i, err) + } else if !test.Error && !FieldValueEquals(field, test.Out) { + t.Errorf("test %d: field values don't match. expected: '%+v', actual: '%+v'", i, test.Out.GetValue(), test.In.GetValue()) } - - // TODO: check that value was correctly found } } diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index 57c8bda..f47182f 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -390,10 +390,11 @@ func (*DocumentInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []in // Parameter allows user input to be used in producing the output of the field. type Parameter struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Prompt string `protobuf:"bytes,2,opt,name=prompt" json:"prompt,omitempty"` - Pattern string `protobuf:"bytes,3,opt,name=pattern" json:"pattern,omitempty"` - Required bool `protobuf:"varint,4,opt,name=required" json:"required,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Prompt string `protobuf:"bytes,2,opt,name=prompt" json:"prompt,omitempty"` + Pattern string `protobuf:"bytes,3,opt,name=pattern" json:"pattern,omitempty"` + // Default is the value a parameter will take if no argument is given for it. Parameters without defaults require args. + Default *Argument `protobuf:"bytes,4,opt,name=default" json:"default,omitempty"` } func (m *Parameter) Reset() { *m = Parameter{} } @@ -401,6 +402,13 @@ func (m *Parameter) String() string { return proto.CompactTextString( func (*Parameter) ProtoMessage() {} func (*Parameter) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } +func (m *Parameter) GetDefault() *Argument { + if m != nil { + return m.Default + } + return nil +} + // Argument contains an argument to fulfill a parameter. type Argument struct { // Types that are valid to be assigned to Value: @@ -557,35 +565,35 @@ func init() { } var fileDescriptor0 = []byte{ - // 468 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x53, 0x4d, 0x6f, 0xd3, 0x40, - 0x10, 0xc5, 0xf1, 0x47, 0x9c, 0x71, 0x52, 0x52, 0xc3, 0x61, 0x81, 0x08, 0x45, 0x2b, 0x21, 0xf5, - 0x94, 0x03, 0xbd, 0x20, 0x38, 0x51, 0xf1, 0x15, 0x54, 0x01, 0x82, 0x13, 0xdc, 0x36, 0xc9, 0x34, - 0x35, 0x89, 0xbd, 0x66, 0xbc, 0xa9, 0x94, 0xdf, 0xcd, 0x1f, 0x60, 0x76, 0x6d, 0xe3, 0x58, 0x3d, - 0x59, 0x3b, 0xf3, 0xf6, 0xbd, 0x7d, 0x6f, 0xc6, 0x30, 0xd6, 0xab, 0xdf, 0xb8, 0x36, 0x8b, 0x92, - 0xb4, 0xd1, 0x69, 0x54, 0x95, 0x84, 0x6a, 0x23, 0xff, 0x7a, 0x10, 0x7e, 0xc8, 0x70, 0xbf, 0x49, - 0x13, 0xf0, 0x77, 0x78, 0x14, 0xde, 0xdc, 0xbb, 0x18, 0xa5, 0x53, 0x88, 0x8a, 0x43, 0xbe, 0x42, - 0x12, 0x03, 0x3e, 0x7b, 0x9f, 0x1e, 0xa4, 0x13, 0xf0, 0x2b, 0x43, 0xc2, 0xb7, 0x6d, 0x3e, 0x9e, - 0xc3, 0x70, 0xa5, 0xf5, 0x1e, 0x55, 0x21, 0x02, 0x2e, 0xc5, 0x5c, 0x9a, 0x43, 0x54, 0x4b, 0x88, - 0x90, 0x2b, 0xc9, 0xcb, 0xb3, 0x45, 0xad, 0xb1, 0xf8, 0xea, 0xaa, 0x8c, 0x78, 0x0e, 0xa1, 0x22, - 0x52, 0x47, 0x11, 0x39, 0xc0, 0xa4, 0x05, 0xbc, 0xb5, 0x45, 0xee, 0xcf, 0x20, 0xd8, 0x67, 0xc5, - 0x4e, 0x0c, 0x5d, 0x7b, 0xdc, 0xb6, 0xaf, 0xb9, 0xe6, 0xf8, 0xc3, 0x52, 0x91, 0xca, 0x45, 0xec, - 0xda, 0xe7, 0x6d, 0xfb, 0x9b, 0x2d, 0xa2, 0x41, 0x62, 0xfe, 0x40, 0xd1, 0xb6, 0x12, 0xa3, 0xb9, - 0xcf, 0x80, 0x69, 0x47, 0xbf, 0x3d, 0xe4, 0x58, 0x98, 0xab, 0x21, 0x84, 0x77, 0x6a, 0x7f, 0x40, - 0xa9, 0x21, 0xaa, 0x1f, 0x95, 0x5e, 0x40, 0x98, 0x19, 0xcc, 0x2b, 0xf6, 0x6d, 0xef, 0x3c, 0xe9, - 0xbf, 0x79, 0xb1, 0xb4, 0xbd, 0xf7, 0x85, 0xa1, 0xe3, 0xd3, 0x37, 0x00, 0xdd, 0xa9, 0x9f, 0xd6, - 0xac, 0xe1, 0x75, 0x61, 0x9d, 0xf8, 0x72, 0xc1, 0xbe, 0x1e, 0xbc, 0xf2, 0xe4, 0x0b, 0x08, 0x9d, - 0x49, 0x0b, 0x3d, 0xd5, 0xeb, 0x43, 0xe5, 0x25, 0xf8, 0x3f, 0xbe, 0x2f, 0xd3, 0x87, 0x30, 0x34, - 0x84, 0x98, 0x55, 0xb7, 0x8d, 0xc0, 0x18, 0x82, 0x52, 0x99, 0x5b, 0xc7, 0x3f, 0xe2, 0x51, 0x84, - 0x37, 0x16, 0x5e, 0x0f, 0x43, 0x7e, 0x86, 0xc0, 0x26, 0x94, 0x3e, 0x82, 0xa4, 0x54, 0xeb, 0x9d, - 0xda, 0xe2, 0x17, 0xce, 0xa3, 0xb9, 0xf9, 0x0c, 0x22, 0xc3, 0x99, 0xa0, 0x69, 0xde, 0x96, 0xb4, - 0x82, 0x56, 0x67, 0x0a, 0xb1, 0xbe, 0x43, 0xa2, 0x6c, 0x83, 0x8e, 0x2b, 0x96, 0x3f, 0x21, 0x7e, - 0xa7, 0xd7, 0x2e, 0x2d, 0x2b, 0x5a, 0x74, 0x44, 0x12, 0x82, 0xac, 0xb8, 0xd1, 0x0d, 0xcd, 0xe3, - 0x96, 0xa6, 0x45, 0x2f, 0xb9, 0xc7, 0x62, 0x01, 0x69, 0x6d, 0x1c, 0xd7, 0x3d, 0x6f, 0x33, 0x18, - 0xf7, 0xc0, 0xad, 0x27, 0x47, 0x2f, 0xaf, 0x61, 0xd4, 0xcd, 0xb1, 0xaf, 0x7c, 0x06, 0x11, 0xef, - 0x6c, 0x5e, 0x9a, 0xc6, 0x3e, 0xa7, 0xc3, 0x17, 0x19, 0x57, 0xd4, 0x01, 0x58, 0x1b, 0x84, 0x7f, - 0x0e, 0x19, 0xe1, 0xa6, 0x5e, 0x46, 0xf9, 0x11, 0xe2, 0x76, 0xe8, 0x27, 0xab, 0xec, 0xf5, 0x57, - 0x79, 0x70, 0x7f, 0x95, 0xfd, 0x7a, 0x95, 0xff, 0x2f, 0xca, 0xd5, 0xe4, 0x57, 0x52, 0x9b, 0x70, - 0x7f, 0xcd, 0x2a, 0x72, 0x9f, 0xcb, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xf6, 0xe0, 0xed, - 0x4c, 0x03, 0x00, 0x00, + // 471 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x53, 0x4d, 0x8f, 0xd3, 0x30, + 0x10, 0x25, 0xcd, 0x67, 0x27, 0xed, 0xd2, 0x0d, 0x1c, 0x0c, 0x54, 0xa8, 0x58, 0x42, 0xda, 0x53, + 0x0f, 0xec, 0x05, 0xc1, 0x89, 0x15, 0x5f, 0x45, 0x08, 0x10, 0x9c, 0x40, 0xe2, 0xe0, 0xb6, 0xd3, + 0x6e, 0x68, 0x12, 0x47, 0x8e, 0xbb, 0x52, 0x7f, 0x37, 0x7f, 0x80, 0xb1, 0x13, 0xd3, 0x46, 0xbb, + 0xa7, 0xd6, 0xf3, 0x9e, 0xdf, 0xf3, 0xbc, 0x99, 0xc0, 0x48, 0x2e, 0xff, 0xe0, 0x4a, 0xcf, 0x6b, + 0x25, 0xb5, 0xcc, 0xa2, 0xa6, 0x56, 0x28, 0xd6, 0xfc, 0xaf, 0x07, 0xe1, 0xfb, 0x1c, 0x8b, 0x75, + 0x96, 0x82, 0xbf, 0xc3, 0x03, 0xf3, 0x66, 0xde, 0xc5, 0x30, 0x9b, 0x40, 0x54, 0xed, 0xcb, 0x25, + 0x2a, 0x36, 0xa0, 0xb3, 0xf7, 0xf1, 0x5e, 0x36, 0x06, 0xbf, 0xd1, 0x8a, 0xf9, 0x06, 0xa6, 0xe3, + 0x39, 0xc4, 0x4b, 0x29, 0x0b, 0x14, 0x15, 0x0b, 0xa8, 0x94, 0x50, 0x69, 0x06, 0x51, 0x6b, 0xc1, + 0x42, 0xaa, 0xa4, 0x2f, 0xce, 0xe6, 0xad, 0xc7, 0xfc, 0xab, 0xad, 0x12, 0xe3, 0x29, 0x84, 0x42, + 0x29, 0x71, 0x60, 0x91, 0x25, 0x8c, 0x1d, 0xe1, 0x8d, 0x29, 0x12, 0x3e, 0x85, 0xa0, 0xc8, 0xab, + 0x1d, 0x8b, 0x2d, 0x3c, 0x72, 0xf0, 0x67, 0xaa, 0x59, 0xfd, 0xb0, 0x16, 0x4a, 0x94, 0x2c, 0xb1, + 0xf0, 0xb9, 0x83, 0xbf, 0x99, 0x22, 0x6a, 0x54, 0xa4, 0x1f, 0x08, 0xb5, 0x6d, 0xd8, 0x70, 0xe6, + 0x13, 0x61, 0x72, 0x94, 0xdf, 0xee, 0x4b, 0xac, 0xf4, 0x55, 0x0c, 0xe1, 0x8d, 0x28, 0xf6, 0xc8, + 0x25, 0x44, 0xed, 0xa3, 0xb2, 0x0b, 0x08, 0x73, 0x8d, 0x65, 0x43, 0x7d, 0x9b, 0x3b, 0x8f, 0xfa, + 0x6f, 0x9e, 0x2f, 0x0c, 0xf6, 0xae, 0xd2, 0xea, 0xf0, 0xf8, 0x35, 0xc0, 0xf1, 0xd4, 0x4f, 0x6b, + 0xda, 0xe9, 0xda, 0xb0, 0x4e, 0xfa, 0xb2, 0xc1, 0xbe, 0x1a, 0xbc, 0xf4, 0xf8, 0x73, 0x08, 0x6d, + 0x93, 0x86, 0x7a, 0xea, 0xd7, 0xa7, 0xf2, 0x4b, 0xf0, 0x7f, 0x7c, 0x5f, 0x64, 0xf7, 0x21, 0xd6, + 0x0a, 0x31, 0x6f, 0xae, 0x3b, 0x83, 0x11, 0x04, 0xb5, 0xd0, 0xd7, 0x56, 0x7f, 0x48, 0xa3, 0x08, + 0x37, 0x86, 0xde, 0x0e, 0x83, 0x7f, 0x82, 0xc0, 0x24, 0x94, 0x3d, 0x80, 0xb4, 0x16, 0xab, 0x9d, + 0xd8, 0xe2, 0x17, 0xca, 0xa3, 0xbb, 0xf9, 0x04, 0x22, 0x4d, 0x99, 0xa0, 0xee, 0xde, 0x96, 0x3a, + 0x43, 0xe3, 0x33, 0x81, 0x44, 0xde, 0xa0, 0x52, 0xf9, 0x1a, 0xad, 0x56, 0xc2, 0x7f, 0x42, 0xf2, + 0x56, 0xae, 0x6c, 0x5a, 0xc6, 0xb4, 0x3a, 0x0a, 0x71, 0x08, 0xf2, 0x6a, 0x23, 0x3b, 0x99, 0x87, + 0x4e, 0xc6, 0xb1, 0x17, 0x84, 0x91, 0x59, 0xa0, 0xa4, 0xd4, 0x56, 0xeb, 0x56, 0x6f, 0x53, 0x18, + 0xf5, 0xc8, 0xae, 0x27, 0x2b, 0xcf, 0x7f, 0xc3, 0xf0, 0x38, 0xc7, 0xbe, 0xf3, 0x19, 0x44, 0xb4, + 0xb3, 0x65, 0xad, 0xbb, 0xf6, 0x29, 0x1d, 0xba, 0x48, 0xbc, 0xaa, 0x0d, 0x20, 0x7b, 0x06, 0xf1, + 0x1a, 0x37, 0x62, 0x5f, 0x68, 0xbb, 0x8b, 0x77, 0x4c, 0x9e, 0x7f, 0x80, 0xc4, 0xfd, 0x3f, 0xd9, + 0x6d, 0xaf, 0xbf, 0xdb, 0x83, 0xdb, 0xbb, 0xed, 0xb7, 0xbb, 0xfd, 0x7f, 0x73, 0xae, 0xc6, 0xbf, + 0xd2, 0x56, 0xdb, 0x7e, 0x46, 0xcb, 0xc8, 0xfe, 0x5c, 0xfe, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xd8, + 0x3c, 0xfc, 0xc4, 0x5d, 0x03, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto index 1d661b3..71af5ab 100644 --- a/proto/object.proto +++ b/proto/object.proto @@ -67,7 +67,8 @@ message Parameter { string name = 1; string prompt = 2; string pattern = 3; - bool required = 4; + // Default is the value a parameter will take if no argument is given for it. Parameters without defaults require args. + Argument default = 4; } // Argument contains an argument to fulfill a parameter. From 6f5b3f1db87a4be0bef0fba4f356aae171bbee4a Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sun, 24 Jul 2016 13:29:21 -0700 Subject: [PATCH 68/79] added command to add parameters --- cli/param.go | 73 +++++++++++++++++++++++++++++++++++++++++++ pkg/data/parameter.go | 29 +++++++++++++++++ pkg/project/index.go | 24 ++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 cli/param.go diff --git a/cli/param.go b/cli/param.go new file mode 100644 index 0000000..647a133 --- /dev/null +++ b/cli/param.go @@ -0,0 +1,73 @@ +package cli + +import ( + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/data" + pb "rsprd.com/spread/pkg/spreadproto" +) + +// Param allows the parameters to be created on the Index +func (s SpreadCli) Param() *cli.Command { + return &cli.Command{ + Name: "param", + Usage: "Set paramaters for field values in the index", + ArgsUsage: " ", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "f", + Usage: "set Golang format string to use with arguments", + }, + cli.StringFlag{ + Name: "d", + Usage: "set default value, interpretted as JSON", + }, + }, + Action: func(c *cli.Context) { + if len(c.Args()) < 3 { + s.fatalf("an srl, name, and description must be provided") + } + + targetUrl := c.Args().First() + if len(targetUrl) == 0 { + s.fatalf("A target SRI must be specified") + } + + target, err := data.ParseSRI(targetUrl) + if err != nil { + s.fatalf("Error using target: %v", err) + } + + proj := s.projectOrDie() + doc, err := proj.DocFromIndex(target.Path) + if err != nil { + s.fatalf("Error retrieving from index: %v", err) + } + + // parse default value + var def *pb.Argument + defaultInput := c.String("d") + if len(defaultInput) != 0 { + def, err = data.ParseArgument(defaultInput) + if err != nil { + s.fatalf("Could not parse default value: %v", err) + } + } + + param := &pb.Parameter{ + Name: c.Args().Get(1), + Prompt: c.Args().Get(2), + Default: def, + Pattern: c.String("f"), + } + + if err = data.AddParamToDoc(doc, target, param); err != nil { + s.fatalf("Failed to add parameter: %v", err) + } + + if err = proj.AddDocumentToIndex(doc); err != nil { + s.fatalf("Failed to add object to Git index: %v", err) + } + }, + } +} diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index 747deef..3c85719 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -1,6 +1,7 @@ package data import ( + "encoding/json" "errors" "fmt" @@ -57,6 +58,34 @@ func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { return nil } +// ParseArgument returns an argument parsed from JSON. +func ParseArgument(in string) (*pb.Argument, error) { + var data interface{} + err := json.Unmarshal([]byte(in), &data) + if err != nil { + return nil, err + } + + arg := &pb.Argument{} + switch typedData := data.(type) { + case bool: + arg.Value = &pb.Argument_Boolean{ + Boolean: typedData, + } + case float64: + arg.Value = &pb.Argument_Number{ + Number: typedData, + } + case string: + arg.Value = &pb.Argument_Str{ + Str: typedData, + } + default: + return nil, errors.New("unknown type") + } + return arg, nil +} + // simpleArgApply is used when no formatting template string is given. func simpleArgApply(field *pb.Field, arg *pb.Argument) error { switch val := arg.GetValue().(type) { diff --git a/pkg/project/index.go b/pkg/project/index.go index 52b8bf6..cf906b2 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -69,6 +69,30 @@ func (p *Project) Index() (*deploy.Deployment, error) { return deployment, nil } +func (p *Project) DocFromIndex(path string) (*pb.Document, error) { + index, err := p.repo.Index() + if err != nil { + return nil, fmt.Errorf("could not retrieve index: %v", err) + } + + indexSize := int(index.EntryCount()) + for i := 0; i < indexSize; i++ { + entry, err := index.EntryByIndex(uint(i)) + if err != nil { + return nil, fmt.Errorf("failed to retrieve index entry: %v", err) + } + + if entry.Path == path { + doc, err := p.getDocument(entry.Id) + if err != nil { + return nil, fmt.Errorf("failed to read object from Git repository: %v", err) + } + return doc, nil + } + } + return nil, fmt.Errorf("could not find document with path '%s'", path) +} + func kindFromPath(path string) (string, error) { parts := strings.Split(path, "/") if len(parts) != 4 { From 52740e999e819639371b271805c9c4e50c330f9d Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Sun, 24 Jul 2016 23:54:38 -0700 Subject: [PATCH 69/79] removed Kubernetes dependency from VCS package --- cli/deploy.go | 18 ++++++++++++++---- cli/diff.go | 7 ++++++- cli/link.go | 23 ++++++----------------- cli/status.go | 15 +++++++++++++-- pkg/deploy/deployment.go | 20 ++++++++++++++++++++ pkg/deploy/object.go | 17 +++++++++++++++-- pkg/project/branch.go | 7 +++---- pkg/project/commit.go | 17 ++++++----------- pkg/project/document.go | 19 ------------------- pkg/project/index.go | 23 ++++------------------- pkg/project/tree.go | 22 +++++++++------------- 11 files changed, 96 insertions(+), 92 deletions(-) diff --git a/cli/deploy.go b/cli/deploy.go index ad93e80..2016091 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -27,10 +27,15 @@ func (s SpreadCli) Deploy() *cli.Command { if err == nil { if len(ref) == 0 { s.printf("Deploying from index...") - dep, err = proj.Index() + docs, err := proj.Index() + if err != nil { + s.fatalf("Error getting index: %v", err) + } + + dep, err = deploy.DeploymentFromDocMap(docs) } else { - if commit, err := proj.ResolveCommit(ref); err == nil { - dep = commit + if docs, err := proj.ResolveCommit(ref); err == nil { + dep, err = deploy.DeploymentFromDocMap(docs) } else { dep, err = s.globalDeploy(ref) } @@ -134,7 +139,12 @@ func (s SpreadCli) globalDeploy(ref string) (*deploy.Deployment, error) { return nil, fmt.Errorf("failed to fetch '%s': %v", ref, err) } - return proj.Branch(branch) + docs, err := proj.Branch(branch) + if err != nil { + return nil, err + } + + return deploy.DeploymentFromDocMap(docs) } } return dep, err diff --git a/cli/diff.go b/cli/diff.go index f333d27..1986ff4 100644 --- a/cli/diff.go +++ b/cli/diff.go @@ -21,11 +21,16 @@ func (s SpreadCli) Diff() *cli.Command { }, Action: func(c *cli.Context) { proj := s.projectOrDie() - index, err := proj.Index() + docs, err := proj.Index() if err != nil { s.fatalf("Could not load Index: %v", err) } + index, err := deploy.DeploymentFromDocMap(docs) + if err != nil { + s.fatalf("Failed to create Deployment from Documents: %v", err) + } + context := c.String("context") client, err := deploy.NewKubeClusterFromContext(context) if err != nil { diff --git a/cli/link.go b/cli/link.go index 7f429a0..360a0a5 100644 --- a/cli/link.go +++ b/cli/link.go @@ -4,7 +4,6 @@ import ( "github.com/codegangsta/cli" "rsprd.com/spread/pkg/data" - "rsprd.com/spread/pkg/deploy" ) // Link allows the links to be created on the Index @@ -35,32 +34,22 @@ func (s SpreadCli) Link() *cli.Command { } proj := s.projectOrDie() - dep, err := proj.Index() + index, err := proj.Index() if err != nil { s.fatalf("Error retrieving index: %v", err) } - kubeObj, err := dep.Get(attach.Path) - if err != nil { - s.fatalf("Could not get object: %v", err) - } - - path, err := deploy.ObjectPath(kubeObj) - if err != nil { - s.fatalf("Failed to determine path to save object: %v", err) - } - - obj, err := data.CreateDocument(kubeObj.GetObjectMeta().GetName(), path, kubeObj) - if err != nil { - s.fatalf("failed to encode object: %v", err) + doc, ok := index[attach.Path] + if !ok { + s.fatalf("Path '%s' not found", attach.Path) } link := data.NewLink("test", target, false) - if err = data.CreateLinkInDocument(obj, link, attach); err != nil { + if err = data.CreateLinkInDocument(doc, link, attach); err != nil { s.fatalf("Could not create link: %v", err) } - if err = proj.AddDocumentToIndex(obj); err != nil { + if err = proj.AddDocumentToIndex(doc); err != nil { s.fatalf("Failed to add object to Git index: %v", err) } }, diff --git a/cli/status.go b/cli/status.go index be6ccee..a032da0 100644 --- a/cli/status.go +++ b/cli/status.go @@ -14,13 +14,24 @@ func (s SpreadCli) Status() *cli.Command { Description: "Information about what's commited, changed, and staged.", Action: func(c *cli.Context) { proj := s.projectOrDie() - index, err := proj.Index() + indexDocs, err := proj.Index() if err != nil { s.fatalf("Could not load Index: %v", err) } - head, err := proj.Head() + index, err := deploy.DeploymentFromDocMap(indexDocs) if err != nil { + s.fatalf("Failed to create KubeObject from index doc: %v", err) + } + + var head *deploy.Deployment + headDocs, err := proj.Head() + if err == nil { + head, err = deploy.DeploymentFromDocMap(headDocs) + if err != nil { + s.fatalf("Failed to create KubeObject from HEAD doc: %v", err) + } + } else { head = new(deploy.Deployment) } diff --git a/pkg/deploy/deployment.go b/pkg/deploy/deployment.go index eda858a..a123737 100644 --- a/pkg/deploy/deployment.go +++ b/pkg/deploy/deployment.go @@ -7,8 +7,28 @@ import ( "github.com/pmezard/go-difflib/difflib" kube "k8s.io/kubernetes/pkg/api" + + pb "rsprd.com/spread/pkg/spreadproto" ) +// DeploymentFromDocMap produces a new Deployment from a map of Documents. +func DeploymentFromDocMap(docs map[string]*pb.Document) (deploy *Deployment, err error) { + var obj KubeObject + for path, doc := range docs { + doc.GetInfo().Path = path + obj, err = KubeObjectFromDocument(path, doc) + if err != nil { + return + } + + err = deploy.Add(obj) + if err != nil { + return + } + } + return +} + // A Deployment is a representation of a Kubernetes cluster's object registry. // It can be used to specify objects to be deployed and is how the current state of a deployment is returned. // The key of objects is formed by "/namespaces///" diff --git a/pkg/deploy/object.go b/pkg/deploy/object.go index d9377f6..ce4817e 100644 --- a/pkg/deploy/object.go +++ b/pkg/deploy/object.go @@ -46,15 +46,28 @@ func objectKind(obj KubeObject) (types.GroupVersionKind, error) { return gkv, nil } -func KubeObjectFromDocument(kind string, doc *pb.Document) (KubeObject, error) { +func KubeObjectFromDocument(path string, doc *pb.Document) (KubeObject, error) { + kind, err := kindFromPath(path) + if err != nil { + return nil, err + } + base := BaseObject(kind) if base == nil { return nil, fmt.Errorf("unable to find Kind for '%s'", kind) } - err := data.Unmarshal(doc, &base) + err = data.Unmarshal(doc, &base) if err != nil { return nil, err } return base, nil } + +func kindFromPath(path string) (string, error) { + parts := strings.Split(path, "/") + if len(parts) != 4 { + return "", fmt.Errorf("path wrong length (is %d, expected 5)", len(parts)) + } + return parts[2], nil +} diff --git a/pkg/project/branch.go b/pkg/project/branch.go index 6715afc..b5710dd 100644 --- a/pkg/project/branch.go +++ b/pkg/project/branch.go @@ -4,11 +4,10 @@ import ( "fmt" git "gopkg.in/libgit2/git2go.v23" - - "rsprd.com/spread/pkg/deploy" + pb "rsprd.com/spread/pkg/spreadproto" ) -func (p *Project) Branch(name string) (*deploy.Deployment, error) { +func (p *Project) Branch(name string) (map[string]*pb.Document, error) { br, err := p.repo.LookupBranch(name, git.BranchRemote) if err != nil { return nil, fmt.Errorf("unable to locate branch: %v", err) @@ -19,5 +18,5 @@ func (p *Project) Branch(name string) (*deploy.Deployment, error) { return nil, err } - return p.deploymentFromTree(tree.(*git.Tree)) + return p.mapFromTree(tree.(*git.Tree)) } diff --git a/pkg/project/commit.go b/pkg/project/commit.go index dcbe406..a591a4e 100644 --- a/pkg/project/commit.go +++ b/pkg/project/commit.go @@ -5,7 +5,7 @@ import ( git "gopkg.in/libgit2/git2go.v23" - "rsprd.com/spread/pkg/deploy" + pb "rsprd.com/spread/pkg/spreadproto" ) func (p *Project) Commit(refname string, author, committer Person, message string) (commitOid string, err error) { @@ -47,7 +47,7 @@ func (p *Project) writeIndex() (*git.Tree, error) { return tree, nil } -func (p *Project) Head() (*deploy.Deployment, error) { +func (p *Project) Head() (map[string]*pb.Document, error) { commit, err := p.headCommit() if err != nil { return nil, fmt.Errorf("could not retrieve head: %v", err) @@ -58,10 +58,10 @@ func (p *Project) Head() (*deploy.Deployment, error) { return nil, fmt.Errorf("couldn't get tree for HEAD: %v", err) } - return p.deploymentFromTree(tree) + return p.mapFromTree(tree) } -func (p *Project) ResolveCommit(revision string) (*deploy.Deployment, error) { +func (p *Project) ResolveCommit(revision string) (map[string]*pb.Document, error) { gitObj, err := p.repo.RevparseSingle(revision) if err != nil { return nil, fmt.Errorf("couldn't resolve revspec '%s': %v", revision, err) @@ -71,17 +71,12 @@ func (p *Project) ResolveCommit(revision string) (*deploy.Deployment, error) { return nil, fmt.Errorf("'%s' specifies an object other than a commit", revision) } - commit, err := gitObj.Peel(git.ObjectCommit) + tree, err := gitObj.Peel(git.ObjectTree) if err != nil { return nil, err } - tree, err := commit.(*git.Commit).Tree() - if err != nil { - return nil, err - } - - return p.deploymentFromTree(tree) + return p.mapFromTree(tree.(*git.Tree)) } func (p *Project) headCommit() (*git.Commit, error) { diff --git a/pkg/project/document.go b/pkg/project/document.go index 2729a6b..7d4021a 100644 --- a/pkg/project/document.go +++ b/pkg/project/document.go @@ -6,7 +6,6 @@ import ( "github.com/golang/protobuf/proto" git "gopkg.in/libgit2/git2go.v23" - "rsprd.com/spread/pkg/deploy" pb "rsprd.com/spread/pkg/spreadproto" ) @@ -39,21 +38,3 @@ func (p *Project) createDocument(obj *pb.Document) (oid *git.Oid, size int, err } return } - -func (p *Project) getKubeObject(oid *git.Oid, path string) (deploy.KubeObject, error) { - doc, err := p.getDocument(oid) - if err != nil { - return nil, fmt.Errorf("failed to read object from Git repository: %v", err) - } - - kind, err := kindFromPath(path) - if err != nil { - return nil, err - } - - kubeObj, err := deploy.KubeObjectFromDocument(kind, doc) - if err != nil { - return nil, err - } - return kubeObj, nil -} diff --git a/pkg/project/index.go b/pkg/project/index.go index cf906b2..3f0c0ab 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -3,11 +3,9 @@ package project import ( "errors" "fmt" - "strings" git "gopkg.in/libgit2/git2go.v23" - "rsprd.com/spread/pkg/deploy" pb "rsprd.com/spread/pkg/spreadproto" ) @@ -42,13 +40,12 @@ func (p *Project) AddDocumentToIndex(doc *pb.Document) error { return index.Write() } -func (p *Project) Index() (*deploy.Deployment, error) { +func (p *Project) Index() (docs map[string]*pb.Document, err error) { index, err := p.repo.Index() if err != nil { return nil, fmt.Errorf("could not retrieve index: %v", err) } - deployment := new(deploy.Deployment) indexSize := int(index.EntryCount()) for i := 0; i < indexSize; i++ { entry, err := index.EntryByIndex(uint(i)) @@ -56,17 +53,13 @@ func (p *Project) Index() (*deploy.Deployment, error) { return nil, fmt.Errorf("failed to retrieve index entry: %v", err) } - kubeObj, err := p.getKubeObject(entry.Id, entry.Path) + doc, err := p.getDocument(entry.Id) if err != nil { return nil, err } - - err = deployment.Add(kubeObj) - if err != nil { - return nil, fmt.Errorf("could not add object to deployment: %v", err) - } + docs[entry.Path] = doc } - return deployment, nil + return } func (p *Project) DocFromIndex(path string) (*pb.Document, error) { @@ -93,14 +86,6 @@ func (p *Project) DocFromIndex(path string) (*pb.Document, error) { return nil, fmt.Errorf("could not find document with path '%s'", path) } -func kindFromPath(path string) (string, error) { - parts := strings.Split(path, "/") - if len(parts) != 4 { - return "", fmt.Errorf("path wrong length (is %d, expected 5)", len(parts)) - } - return parts[2], nil -} - var ( ErrNilObjectInfo = errors.New("an object's Info field cannot be nil") ) diff --git a/pkg/project/tree.go b/pkg/project/tree.go index 2813710..ebb1926 100644 --- a/pkg/project/tree.go +++ b/pkg/project/tree.go @@ -5,22 +5,21 @@ import ( git "gopkg.in/libgit2/git2go.v23" - "rsprd.com/spread/pkg/deploy" + pb "rsprd.com/spread/pkg/spreadproto" ) -func (p *Project) deploymentFromTree(tree *git.Tree) (*deploy.Deployment, error) { - deployment := new(deploy.Deployment) +func (p *Project) mapFromTree(tree *git.Tree) (docs map[string]*pb.Document, err error) { var walkErr error - err := tree.Walk(func(path string, entry *git.TreeEntry) int { + err = tree.Walk(func(path string, entry *git.TreeEntry) int { // add objects to deployment if entry.Type == git.ObjectBlob { - kubeObj, err := p.getKubeObject(entry.Id, path) + doc, err := p.getDocument(entry.Id) if err != nil { walkErr = err return -1 } - walkErr = deployment.Add(kubeObj) + docs[path] = doc if walkErr != nil { return -1 } @@ -29,12 +28,9 @@ func (p *Project) deploymentFromTree(tree *git.Tree) (*deploy.Deployment, error) }) if err != nil { - return nil, fmt.Errorf("error starting walk: %v", err) + err = fmt.Errorf("error starting walk: %v", err) + } else if walkErr != nil { + err = fmt.Errorf("error during walk: %v", walkErr) } - - if walkErr != nil { - return nil, fmt.Errorf("error during walk: %v", err) - } - - return deployment, nil + return } From e33451a9413f8faeb054df2842a17bde53dc4af1 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Mon, 25 Jul 2016 00:24:41 -0700 Subject: [PATCH 70/79] fix nil deployment --- pkg/deploy/deployment.go | 1 + pkg/project/tree.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/deploy/deployment.go b/pkg/deploy/deployment.go index a123737..02c3c54 100644 --- a/pkg/deploy/deployment.go +++ b/pkg/deploy/deployment.go @@ -14,6 +14,7 @@ import ( // DeploymentFromDocMap produces a new Deployment from a map of Documents. func DeploymentFromDocMap(docs map[string]*pb.Document) (deploy *Deployment, err error) { var obj KubeObject + deploy = new(Deployment) for path, doc := range docs { doc.GetInfo().Path = path obj, err = KubeObjectFromDocument(path, doc) diff --git a/pkg/project/tree.go b/pkg/project/tree.go index ebb1926..5581868 100644 --- a/pkg/project/tree.go +++ b/pkg/project/tree.go @@ -10,6 +10,7 @@ import ( func (p *Project) mapFromTree(tree *git.Tree) (docs map[string]*pb.Document, err error) { var walkErr error + docs = make(map[string]*pb.Document, tree.EntryCount()) err = tree.Walk(func(path string, entry *git.TreeEntry) int { // add objects to deployment if entry.Type == git.ObjectBlob { From 8b4f0d4417eb9caa0b682b17db3cbbb558e83ec3 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Mon, 25 Jul 2016 01:00:54 -0700 Subject: [PATCH 71/79] added show command --- cli/show.go | 43 +++++++++++++++++++++++++++++++++++++++++ pkg/project/document.go | 13 +++++++++++++ pkg/project/index.go | 2 +- pkg/project/tree.go | 1 + 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 cli/show.go diff --git a/cli/show.go b/cli/show.go new file mode 100644 index 0000000..bad82a6 --- /dev/null +++ b/cli/show.go @@ -0,0 +1,43 @@ +package cli + +import ( + "encoding/json" + + "github.com/codegangsta/cli" + + "rsprd.com/spread/pkg/data" +) + +// Show displays data stored in Spread Documents. +func (s SpreadCli) Show() *cli.Command { + return &cli.Command{ + Name: "show", + Usage: "Display information stored in a repository", + ArgsUsage: " ", + Action: func(c *cli.Context) { + if len(c.Args()) < 2 { + s.fatalf("a revison and a path must be specified") + } + p := s.projectOrDie() + + revision := c.Args().First() + path := c.Args().Get(1) + doc, err := p.GetDocument(revision, path) + if err != nil { + s.fatalf("failed to get document: %v", err) + } + + fields, err := data.MapFromDocument(doc) + if err != nil { + s.fatalf("could not get fields: %v", err) + } + + out, err := json.MarshalIndent(&fields, "", "\t") + if err != nil { + s.fatalf("Couldn't create JSON: %v", err) + } + + s.printf("%s", out) + }, + } +} diff --git a/pkg/project/document.go b/pkg/project/document.go index 7d4021a..f46ca8b 100644 --- a/pkg/project/document.go +++ b/pkg/project/document.go @@ -9,6 +9,19 @@ import ( pb "rsprd.com/spread/pkg/spreadproto" ) +func (p *Project) GetDocument(revision, path string) (*pb.Document, error) { + docs, err := p.ResolveCommit(revision) + if err != nil { + return nil, err + } + + doc, has := docs[path] + if !has { + return nil, fmt.Errorf("the path '%s' does not exist in doc", path) + } + return doc, nil +} + func (p *Project) getDocument(oid *git.Oid) (*pb.Document, error) { blob, err := p.repo.LookupBlob(oid) if err != nil { diff --git a/pkg/project/index.go b/pkg/project/index.go index 3f0c0ab..70aa494 100644 --- a/pkg/project/index.go +++ b/pkg/project/index.go @@ -45,8 +45,8 @@ func (p *Project) Index() (docs map[string]*pb.Document, err error) { if err != nil { return nil, fmt.Errorf("could not retrieve index: %v", err) } - indexSize := int(index.EntryCount()) + docs = make(map[string]*pb.Document, indexSize) for i := 0; i < indexSize; i++ { entry, err := index.EntryByIndex(uint(i)) if err != nil { diff --git a/pkg/project/tree.go b/pkg/project/tree.go index 5581868..863e089 100644 --- a/pkg/project/tree.go +++ b/pkg/project/tree.go @@ -20,6 +20,7 @@ func (p *Project) mapFromTree(tree *git.Tree) (docs map[string]*pb.Document, err return -1 } + path = path + entry.Name docs[path] = doc if walkErr != nil { return -1 From 14b4a2853f76d297f146c1b0de29f4bbd3718508 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Mon, 25 Jul 2016 03:39:03 -0700 Subject: [PATCH 72/79] added flag to view parameters --- cli/param.go | 18 ++++++++++++++++++ pkg/data/parameter.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/cli/param.go b/cli/param.go index 647a133..231e690 100644 --- a/cli/param.go +++ b/cli/param.go @@ -14,6 +14,10 @@ func (s SpreadCli) Param() *cli.Command { Usage: "Set paramaters for field values in the index", ArgsUsage: " ", Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "l", + Usage: "list parameters", + }, cli.StringFlag{ Name: "f", Usage: "set Golang format string to use with arguments", @@ -24,6 +28,20 @@ func (s SpreadCli) Param() *cli.Command { }, }, Action: func(c *cli.Context) { + if c.Bool("l") { + p := s.projectOrDie() + docs, err := p.Index() + if err != nil { + s.fatalf("Could not retrieve index: %v", err) + } + + paramFields := data.ParameterFields(docs) + for name := range paramFields { + s.printf(" - %s", name) + } + return + } + if len(c.Args()) < 3 { s.fatalf("an srl, name, and description must be provided") } diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index 3c85719..666e559 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -58,6 +58,35 @@ func ApplyArguments(field *pb.Field, args ...*pb.Argument) error { return nil } +// ParameterFields returns the fields with parameters contained within a Document. +func ParameterFields(docs map[string]*pb.Document) map[string]*pb.Field { + fields := map[string]*pb.Field{} + for _, doc := range docs { + AddParameterFields(doc.GetRoot(), fields) + } + return fields +} + +// AddParameterFields adds fields with parameters from the given field (and its subfields) to the map given. The name of the parameter is the key. +func AddParameterFields(field *pb.Field, params map[string]*pb.Field) { + param := field.GetParam() + // add to map if has parameter + if param != nil { + params[param.Name] = field + } + + switch val := field.GetValue().(type) { + case *pb.Field_Object: + for _, objField := range val.Object.GetItems() { + AddParameterFields(objField, params) + } + case *pb.Field_Array: + for _, arrField := range val.Array.GetItems() { + AddParameterFields(arrField, params) + } + } +} + // ParseArgument returns an argument parsed from JSON. func ParseArgument(in string) (*pb.Argument, error) { var data interface{} From 066b3e0c9abdd030c8527e9996617b2a6642f02c Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 26 Jul 2016 02:29:37 -0700 Subject: [PATCH 73/79] added listing of parameters and started interactive argument prompt --- cli/param.go | 11 +++++++++-- pkg/data/parameter.go | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/cli/param.go b/cli/param.go index 231e690..3b9c4bf 100644 --- a/cli/param.go +++ b/cli/param.go @@ -36,8 +36,15 @@ func (s SpreadCli) Param() *cli.Command { } paramFields := data.ParameterFields(docs) - for name := range paramFields { - s.printf(" - %s", name) + for _, field := range paramFields { + param := field.GetParam() + s.printf(" - Name: %s", param.Name) + s.printf(" Description: %s", param.Prompt) + s.printf(" Pattern: %s", param.Pattern) + if param.GetDefault() == nil { + s.printf(" Required: Yes") + + } } return } diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index 666e559..ea5a9cf 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -4,10 +4,35 @@ import ( "encoding/json" "errors" "fmt" + "io" pb "rsprd.com/spread/pkg/spreadproto" ) +// InteractiveArgs hosts an interactive session using a Reader and Writer which prompts for input which is used to +// populate the provided field. Returns error if receives EOF before completion. +func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field) error { + param := field.GetParam() + fmt.Fprintf(w, "Name: %s\n", param.Name) + fmt.Fprintf(w, "Prompt: %s\n", param.Prompt) + fmt.Fprintf(w, "Input [%s]:\n", displayDefault(param.GetDefault())) + // TODO: READ USER INPUT AND MODIFY FIELD ACCORDINGLY + return nil +} + +func displayDefault(d *pb.Argument) string { + var out interface{} + switch val := d.GetValue().(type) { + case *pb.Argument_Number: + out = val.Number + case *pb.Argument_Str: + out = val.Str + case *pb.Argument_Boolean: + out = val.Boolean + } + return fmt.Sprintf("%v", out) +} + // AddParamToDoc adds the given parameter to the func AddParamToDoc(doc *pb.Document, target *SRI, param *pb.Parameter) error { if !target.IsField() { From fe5dfd3ed55c2475d47dd02bca5b40b2bb9f227f Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 26 Jul 2016 18:31:34 -0700 Subject: [PATCH 74/79] added prompting for arguments --- cli/deploy.go | 37 +++++++++++++--- cli/param.go | 19 +++++---- cli/show.go | 32 ++++++++++---- pkg/data/parameter.go | 59 ++++++++++++++++++++------ pkg/spreadproto/object.pb.go | 82 ++++++++++++++++++------------------ proto/object.proto | 2 +- 6 files changed, 152 insertions(+), 79 deletions(-) diff --git a/cli/deploy.go b/cli/deploy.go index 2016091..94d5f39 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -4,16 +4,18 @@ import ( "errors" "fmt" + "rsprd.com/spread/pkg/data" "rsprd.com/spread/pkg/deploy" "rsprd.com/spread/pkg/entity" "rsprd.com/spread/pkg/input/dir" "rsprd.com/spread/pkg/packages" + pb "rsprd.com/spread/pkg/spreadproto" "github.com/codegangsta/cli" ) // Deploy allows the creation of deploy.Deployments remotely -func (s SpreadCli) Deploy() *cli.Command { +func (s *SpreadCli) Deploy() *cli.Command { return &cli.Command{ Name: "deploy", Usage: "spread deploy [-s] PATH | COMMIT [kubectl context]", @@ -25,17 +27,23 @@ func (s SpreadCli) Deploy() *cli.Command { proj, err := s.project() if err == nil { + var docs map[string]*pb.Document if len(ref) == 0 { s.printf("Deploying from index...") - docs, err := proj.Index() + docs, err = proj.Index() if err != nil { s.fatalf("Error getting index: %v", err) } - dep, err = deploy.DeploymentFromDocMap(docs) - } else { - if docs, err := proj.ResolveCommit(ref); err == nil { + if err = s.promptForArgs(docs, false); err == nil { dep, err = deploy.DeploymentFromDocMap(docs) + } + + } else { + if docs, err = proj.ResolveCommit(ref); err == nil { + if err = s.promptForArgs(docs, false); err == nil { + dep, err = deploy.DeploymentFromDocMap(docs) + } } else { dep, err = s.globalDeploy(ref) } @@ -68,7 +76,7 @@ func (s SpreadCli) Deploy() *cli.Command { } } -func (s SpreadCli) fileDeploy(srcDir string) (*deploy.Deployment, error) { +func (s *SpreadCli) fileDeploy(srcDir string) (*deploy.Deployment, error) { input, err := dir.NewFileInput(srcDir) if err != nil { return nil, inputError(srcDir, err) @@ -100,7 +108,7 @@ func (s SpreadCli) fileDeploy(srcDir string) (*deploy.Deployment, error) { return dep, nil } -func (s SpreadCli) globalDeploy(ref string) (*deploy.Deployment, error) { +func (s *SpreadCli) globalDeploy(ref string) (*deploy.Deployment, error) { // check if reference is local file dep, err := s.fileDeploy(ref) if err != nil { @@ -144,12 +152,27 @@ func (s SpreadCli) globalDeploy(ref string) (*deploy.Deployment, error) { return nil, err } + if err = s.promptForArgs(docs, false); err != nil { + return nil, err + } + return deploy.DeploymentFromDocMap(docs) } } return dep, err } +func (s *SpreadCli) promptForArgs(docs map[string]*pb.Document, required bool) error { + paramFields := data.ParameterFields(docs) + for _, field := range paramFields { + err := data.InteractiveArgs(s.in, s.out, field, required) + if err != nil { + return err + } + } + return nil +} + func objectOnlyDeploy(input *dir.FileInput) (*deploy.Deployment, error) { objects, err := input.Objects() if err != nil { diff --git a/cli/param.go b/cli/param.go index 3b9c4bf..ed47150 100644 --- a/cli/param.go +++ b/cli/param.go @@ -69,21 +69,22 @@ func (s SpreadCli) Param() *cli.Command { s.fatalf("Error retrieving from index: %v", err) } + param := &pb.Parameter{ + Name: c.Args().Get(1), + Prompt: c.Args().Get(2), + Pattern: c.String("f"), + } + // parse default value - var def *pb.Argument defaultInput := c.String("d") if len(defaultInput) != 0 { - def, err = data.ParseArgument(defaultInput) + args, err := data.ParseArguments(defaultInput) if err != nil { s.fatalf("Could not parse default value: %v", err) + } else if len(args) > 1 { + s.fatalf("Only one default value can be specified") } - } - - param := &pb.Parameter{ - Name: c.Args().Get(1), - Prompt: c.Args().Get(2), - Default: def, - Pattern: c.String("f"), + param.Default = args[0] } if err = data.AddParamToDoc(doc, target, param); err != nil { diff --git a/cli/show.go b/cli/show.go index bad82a6..989e75a 100644 --- a/cli/show.go +++ b/cli/show.go @@ -6,6 +6,7 @@ import ( "github.com/codegangsta/cli" "rsprd.com/spread/pkg/data" + pb "rsprd.com/spread/pkg/spreadproto" ) // Show displays data stored in Spread Documents. @@ -15,16 +16,29 @@ func (s SpreadCli) Show() *cli.Command { Usage: "Display information stored in a repository", ArgsUsage: " ", Action: func(c *cli.Context) { - if len(c.Args()) < 2 { - s.fatalf("a revison and a path must be specified") - } + var doc *pb.Document p := s.projectOrDie() - - revision := c.Args().First() - path := c.Args().Get(1) - doc, err := p.GetDocument(revision, path) - if err != nil { - s.fatalf("failed to get document: %v", err) + switch len(c.Args()) { + case 1: // from index + docs, err := p.Index() + if err != nil { + s.fatalf("Failed to get index: %v", err) + } + path := c.Args().First() + var ok bool + if doc, ok = docs[path]; !ok { + s.fatalf("Path '%s' not found in index.", path) + } + case 2: // from revision + revision := c.Args().First() + path := c.Args().Get(1) + var err error + doc, err = p.GetDocument(revision, path) + if err != nil { + s.fatalf("failed to get document: %v", err) + } + default: + s.fatalf("a path OR path and revision must be specified") } fields, err := data.MapFromDocument(doc) diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index ea5a9cf..a03e409 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -1,6 +1,7 @@ package data import ( + "bufio" "encoding/json" "errors" "fmt" @@ -10,14 +11,29 @@ import ( ) // InteractiveArgs hosts an interactive session using a Reader and Writer which prompts for input which is used to -// populate the provided field. Returns error if receives EOF before completion. -func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field) error { +// populate the provided field. If required is true then only fields without defaults will be prompted for. +func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field, required bool) error { param := field.GetParam() - fmt.Fprintf(w, "Name: %s\n", param.Name) - fmt.Fprintf(w, "Prompt: %s\n", param.Prompt) - fmt.Fprintf(w, "Input [%s]:\n", displayDefault(param.GetDefault())) - // TODO: READ USER INPUT AND MODIFY FIELD ACCORDINGLY - return nil + + if required && param.GetDefault() != nil { + return nil + } + + fmt.Fprintln(w, "Name: ", param.Name) + fmt.Fprintln(w, "Prompt: ", param.Prompt) + fmt.Fprintf(w, "Input [%s]: ", displayDefault(param.GetDefault())) + reader := bufio.NewReader(r) + text, err := reader.ReadString('\n') + if err != nil { + return err + } + + args, err := ParseArguments(text) + if err != nil { + return err + } + + return ApplyArguments(field, args...) } func displayDefault(d *pb.Argument) string { @@ -112,14 +128,33 @@ func AddParameterFields(field *pb.Field, params map[string]*pb.Field) { } } -// ParseArgument returns an argument parsed from JSON. -func ParseArgument(in string) (*pb.Argument, error) { +// ParseArguments returns arguments parsed from JSON. +func ParseArguments(in string) (args []*pb.Argument, err error) { var data interface{} - err := json.Unmarshal([]byte(in), &data) + err = json.Unmarshal([]byte(in), &data) if err != nil { - return nil, err + return + } + + var dataArr []interface{} + if arr, isArray := data.([]interface{}); isArray { + dataArr = arr + } else { + dataArr = []interface{}{data} } + args = make([]*pb.Argument, len(dataArr)) + for k, v := range dataArr { + arg, err := argFromJSONType(v) + if err != nil { + return nil, err + } + args[k] = arg + } + return +} + +func argFromJSONType(data interface{}) (*pb.Argument, error) { arg := &pb.Argument{} switch typedData := data.(type) { case bool: @@ -135,7 +170,7 @@ func ParseArgument(in string) (*pb.Argument, error) { Str: typedData, } default: - return nil, errors.New("unknown type") + return nil, fmt.Errorf("unknown type: %+v", typedData) } return arg, nil } diff --git a/pkg/spreadproto/object.pb.go b/pkg/spreadproto/object.pb.go index f47182f..872d6be 100644 --- a/pkg/spreadproto/object.pb.go +++ b/pkg/spreadproto/object.pb.go @@ -46,7 +46,6 @@ type Field struct { // *Field_Link Value isField_Value `protobuf_oneof:"value"` Param *Parameter `protobuf:"bytes,8,opt,name=param" json:"param,omitempty"` - Args []*Argument `protobuf:"bytes,9,rep,name=args" json:"args,omitempty"` } func (m *Field) Reset() { *m = Field{} } @@ -140,13 +139,6 @@ func (m *Field) GetParam() *Parameter { return nil } -func (m *Field) GetArgs() []*Argument { - if m != nil { - return m.Args - } - return nil -} - // XXX_OneofFuncs is for the internal use of the proto package. func (*Field) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { return _Field_OneofMarshaler, _Field_OneofUnmarshaler, _Field_OneofSizer, []interface{}{ @@ -335,9 +327,10 @@ func (*SRI) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } // Link represents a relationship to another field. type Link struct { - PackageName string `protobuf:"bytes,1,opt,name=packageName" json:"packageName,omitempty"` - Target *SRI `protobuf:"bytes,2,opt,name=target" json:"target,omitempty"` - Override bool `protobuf:"varint,3,opt,name=override" json:"override,omitempty"` + PackageName string `protobuf:"bytes,1,opt,name=packageName" json:"packageName,omitempty"` + Target *SRI `protobuf:"bytes,2,opt,name=target" json:"target,omitempty"` + Override bool `protobuf:"varint,3,opt,name=override" json:"override,omitempty"` + Args []*Argument `protobuf:"bytes,4,rep,name=args" json:"args,omitempty"` } func (m *Link) Reset() { *m = Link{} } @@ -352,6 +345,13 @@ func (m *Link) GetTarget() *SRI { return nil } +func (m *Link) GetArgs() []*Argument { + if m != nil { + return m.Args + } + return nil +} + // Document is the root of Spread data stored in a Git blob. It has field stored at it's root, typically with an object as it's value. type Document struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` @@ -566,34 +566,34 @@ func init() { var fileDescriptor0 = []byte{ // 471 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x53, 0x4d, 0x8f, 0xd3, 0x30, - 0x10, 0x25, 0xcd, 0x67, 0x27, 0xed, 0xd2, 0x0d, 0x1c, 0x0c, 0x54, 0xa8, 0x58, 0x42, 0xda, 0x53, - 0x0f, 0xec, 0x05, 0xc1, 0x89, 0x15, 0x5f, 0x45, 0x08, 0x10, 0x9c, 0x40, 0xe2, 0xe0, 0xb6, 0xd3, - 0x6e, 0x68, 0x12, 0x47, 0x8e, 0xbb, 0x52, 0x7f, 0x37, 0x7f, 0x80, 0xb1, 0x13, 0xd3, 0x46, 0xbb, - 0xa7, 0xd6, 0xf3, 0x9e, 0xdf, 0xf3, 0xbc, 0x99, 0xc0, 0x48, 0x2e, 0xff, 0xe0, 0x4a, 0xcf, 0x6b, - 0x25, 0xb5, 0xcc, 0xa2, 0xa6, 0x56, 0x28, 0xd6, 0xfc, 0xaf, 0x07, 0xe1, 0xfb, 0x1c, 0x8b, 0x75, - 0x96, 0x82, 0xbf, 0xc3, 0x03, 0xf3, 0x66, 0xde, 0xc5, 0x30, 0x9b, 0x40, 0x54, 0xed, 0xcb, 0x25, - 0x2a, 0x36, 0xa0, 0xb3, 0xf7, 0xf1, 0x5e, 0x36, 0x06, 0xbf, 0xd1, 0x8a, 0xf9, 0x06, 0xa6, 0xe3, - 0x39, 0xc4, 0x4b, 0x29, 0x0b, 0x14, 0x15, 0x0b, 0xa8, 0x94, 0x50, 0x69, 0x06, 0x51, 0x6b, 0xc1, - 0x42, 0xaa, 0xa4, 0x2f, 0xce, 0xe6, 0xad, 0xc7, 0xfc, 0xab, 0xad, 0x12, 0xe3, 0x29, 0x84, 0x42, - 0x29, 0x71, 0x60, 0x91, 0x25, 0x8c, 0x1d, 0xe1, 0x8d, 0x29, 0x12, 0x3e, 0x85, 0xa0, 0xc8, 0xab, - 0x1d, 0x8b, 0x2d, 0x3c, 0x72, 0xf0, 0x67, 0xaa, 0x59, 0xfd, 0xb0, 0x16, 0x4a, 0x94, 0x2c, 0xb1, - 0xf0, 0xb9, 0x83, 0xbf, 0x99, 0x22, 0x6a, 0x54, 0xa4, 0x1f, 0x08, 0xb5, 0x6d, 0xd8, 0x70, 0xe6, - 0x13, 0x61, 0x72, 0x94, 0xdf, 0xee, 0x4b, 0xac, 0xf4, 0x55, 0x0c, 0xe1, 0x8d, 0x28, 0xf6, 0xc8, - 0x25, 0x44, 0xed, 0xa3, 0xb2, 0x0b, 0x08, 0x73, 0x8d, 0x65, 0x43, 0x7d, 0x9b, 0x3b, 0x8f, 0xfa, - 0x6f, 0x9e, 0x2f, 0x0c, 0xf6, 0xae, 0xd2, 0xea, 0xf0, 0xf8, 0x35, 0xc0, 0xf1, 0xd4, 0x4f, 0x6b, - 0xda, 0xe9, 0xda, 0xb0, 0x4e, 0xfa, 0xb2, 0xc1, 0xbe, 0x1a, 0xbc, 0xf4, 0xf8, 0x73, 0x08, 0x6d, - 0x93, 0x86, 0x7a, 0xea, 0xd7, 0xa7, 0xf2, 0x4b, 0xf0, 0x7f, 0x7c, 0x5f, 0x64, 0xf7, 0x21, 0xd6, - 0x0a, 0x31, 0x6f, 0xae, 0x3b, 0x83, 0x11, 0x04, 0xb5, 0xd0, 0xd7, 0x56, 0x7f, 0x48, 0xa3, 0x08, - 0x37, 0x86, 0xde, 0x0e, 0x83, 0x7f, 0x82, 0xc0, 0x24, 0x94, 0x3d, 0x80, 0xb4, 0x16, 0xab, 0x9d, - 0xd8, 0xe2, 0x17, 0xca, 0xa3, 0xbb, 0xf9, 0x04, 0x22, 0x4d, 0x99, 0xa0, 0xee, 0xde, 0x96, 0x3a, - 0x43, 0xe3, 0x33, 0x81, 0x44, 0xde, 0xa0, 0x52, 0xf9, 0x1a, 0xad, 0x56, 0xc2, 0x7f, 0x42, 0xf2, - 0x56, 0xae, 0x6c, 0x5a, 0xc6, 0xb4, 0x3a, 0x0a, 0x71, 0x08, 0xf2, 0x6a, 0x23, 0x3b, 0x99, 0x87, - 0x4e, 0xc6, 0xb1, 0x17, 0x84, 0x91, 0x59, 0xa0, 0xa4, 0xd4, 0x56, 0xeb, 0x56, 0x6f, 0x53, 0x18, - 0xf5, 0xc8, 0xae, 0x27, 0x2b, 0xcf, 0x7f, 0xc3, 0xf0, 0x38, 0xc7, 0xbe, 0xf3, 0x19, 0x44, 0xb4, - 0xb3, 0x65, 0xad, 0xbb, 0xf6, 0x29, 0x1d, 0xba, 0x48, 0xbc, 0xaa, 0x0d, 0x20, 0x7b, 0x06, 0xf1, - 0x1a, 0x37, 0x62, 0x5f, 0x68, 0xbb, 0x8b, 0x77, 0x4c, 0x9e, 0x7f, 0x80, 0xc4, 0xfd, 0x3f, 0xd9, - 0x6d, 0xaf, 0xbf, 0xdb, 0x83, 0xdb, 0xbb, 0xed, 0xb7, 0xbb, 0xfd, 0x7f, 0x73, 0xae, 0xc6, 0xbf, - 0xd2, 0x56, 0xdb, 0x7e, 0x46, 0xcb, 0xc8, 0xfe, 0x5c, 0xfe, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xd8, - 0x3c, 0xfc, 0xc4, 0x5d, 0x03, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xc5, 0xf1, 0x67, 0xc6, 0x49, 0x49, 0x0d, 0x07, 0x03, 0x11, 0x2a, 0x96, 0x90, 0x7a, 0xca, + 0x81, 0x5e, 0x10, 0x9c, 0xa8, 0xf8, 0x8a, 0x84, 0x00, 0xc1, 0x09, 0x24, 0x0e, 0x9b, 0x64, 0x92, + 0xba, 0xb1, 0xbd, 0xd6, 0x7a, 0x53, 0x29, 0xbf, 0x93, 0x3f, 0xc4, 0xec, 0xd8, 0xdb, 0xc4, 0xb4, + 0xa7, 0x78, 0xe7, 0xbd, 0x99, 0x37, 0xf3, 0x66, 0x02, 0x23, 0xb9, 0xb8, 0xc6, 0xa5, 0x9e, 0xd5, + 0x4a, 0x6a, 0x99, 0x04, 0x4d, 0xad, 0x50, 0xac, 0xb2, 0xbf, 0x0e, 0xf8, 0x1f, 0x73, 0x2c, 0x56, + 0x49, 0x0c, 0xee, 0x16, 0xf7, 0xa9, 0x73, 0xe6, 0x9c, 0x0f, 0x93, 0x09, 0x04, 0xd5, 0xae, 0x5c, + 0xa0, 0x4a, 0x07, 0xf4, 0x76, 0x3e, 0x3f, 0x48, 0xc6, 0xe0, 0x36, 0x5a, 0xa5, 0xae, 0x81, 0xe9, + 0x79, 0x0a, 0xe1, 0x42, 0xca, 0x02, 0x45, 0x95, 0x7a, 0x14, 0x8a, 0x28, 0x74, 0x06, 0x41, 0x2b, + 0x91, 0xfa, 0x14, 0x89, 0x5f, 0x9d, 0xcc, 0x5a, 0x8d, 0xd9, 0x37, 0x8e, 0x12, 0xe3, 0x39, 0xf8, + 0x42, 0x29, 0xb1, 0x4f, 0x03, 0x26, 0x8c, 0x2d, 0xe1, 0x9d, 0x09, 0x12, 0x3e, 0x05, 0xaf, 0xc8, + 0xab, 0x6d, 0x1a, 0x32, 0x3c, 0xb2, 0xf0, 0x17, 0x8a, 0x71, 0x7d, 0xbf, 0x16, 0x4a, 0x94, 0x69, + 0xc4, 0xf0, 0xa9, 0x85, 0xbf, 0x9b, 0x20, 0x6a, 0x54, 0x97, 0x21, 0xf8, 0x37, 0xa2, 0xd8, 0x61, + 0x26, 0x21, 0x68, 0x45, 0x93, 0x73, 0xf0, 0x73, 0x8d, 0x65, 0x43, 0x73, 0xb9, 0x94, 0xf4, 0xa4, + 0xdf, 0xd3, 0x6c, 0x6e, 0xb0, 0x0f, 0x95, 0x56, 0xfb, 0xa7, 0x6f, 0x01, 0x0e, 0xaf, 0xbe, 0x1b, + 0xd3, 0xae, 0x2e, 0x9b, 0x71, 0xd4, 0x37, 0x1b, 0xf7, 0x66, 0xf0, 0xda, 0xc9, 0x5e, 0x82, 0xcf, + 0x43, 0x18, 0xea, 0xb1, 0x5e, 0x9f, 0x9a, 0x5d, 0x80, 0xfb, 0xf3, 0xc7, 0x3c, 0x79, 0x08, 0xa1, + 0x56, 0x88, 0x79, 0x73, 0xd5, 0x09, 0x8c, 0xc0, 0xab, 0x85, 0xbe, 0xe2, 0xfa, 0x43, 0xb2, 0xda, + 0x5f, 0x1b, 0x7a, 0x6b, 0x76, 0x76, 0x0d, 0x9e, 0x71, 0x20, 0x79, 0x04, 0x71, 0x2d, 0x96, 0x5b, + 0xb1, 0xc1, 0xaf, 0x34, 0x6f, 0x97, 0xf9, 0x0c, 0x02, 0x2d, 0xd4, 0x06, 0x75, 0xd7, 0x5b, 0x6c, + 0x05, 0x8d, 0xce, 0x04, 0x22, 0x79, 0x83, 0x4a, 0xe5, 0x2b, 0xe4, 0x5a, 0x11, 0x6d, 0xc0, 0x23, + 0x76, 0x43, 0x3b, 0x33, 0xdd, 0x4d, 0x0e, 0x0b, 0xd8, 0xec, 0x4a, 0xac, 0x74, 0xf6, 0x0b, 0xa2, + 0xf7, 0x72, 0xc9, 0xdf, 0xa6, 0xa9, 0xea, 0x20, 0x94, 0x81, 0x97, 0x57, 0x6b, 0xd9, 0xc9, 0x3c, + 0xb6, 0x99, 0x96, 0x3d, 0x27, 0x8c, 0x9a, 0xf1, 0x94, 0x94, 0x9a, 0xb5, 0xee, 0xcc, 0x3e, 0x85, + 0x51, 0x8f, 0x6c, 0x67, 0xe6, 0xf2, 0xd9, 0x1f, 0x18, 0xde, 0xee, 0xf1, 0x3f, 0xe5, 0x13, 0x08, + 0xe8, 0x66, 0xcb, 0x5a, 0x77, 0xf6, 0x90, 0x7b, 0x94, 0x48, 0xbc, 0xaa, 0x35, 0x28, 0x79, 0x01, + 0xe1, 0x0a, 0xd7, 0x62, 0x57, 0x68, 0xbe, 0xc5, 0xfb, 0xe6, 0xfa, 0x04, 0x91, 0xfd, 0x3e, 0xba, + 0x6d, 0xa7, 0x7f, 0xdb, 0x83, 0xbb, 0xb7, 0xed, 0xb6, 0xb7, 0x7d, 0x7b, 0x59, 0x97, 0xe3, 0xdf, + 0x71, 0x5b, 0x9b, 0xff, 0x46, 0x8b, 0x80, 0x7f, 0x2e, 0xfe, 0x05, 0x00, 0x00, 0xff, 0xff, 0xc9, + 0x65, 0x45, 0xb7, 0x5d, 0x03, 0x00, 0x00, } diff --git a/proto/object.proto b/proto/object.proto index 71af5ab..e04aa9e 100644 --- a/proto/object.proto +++ b/proto/object.proto @@ -23,7 +23,6 @@ message Field { Link link = 7; } Parameter param = 8; - repeated Argument args = 9; } // Object represents a map with strings for keys. @@ -48,6 +47,7 @@ message Link { string packageName = 1; SRI target = 2; bool override = 3; + repeated Argument args = 4; } // Document is the root of Spread data stored in a Git blob. It has field stored at it's root, typically with an object as it's value. From a8e94c22f734ef91256b0911333a19c7726a90f5 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Tue, 26 Jul 2016 18:48:47 -0700 Subject: [PATCH 75/79] allow usage of default for argument by hitting enter --- pkg/data/parameter.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index a03e409..4a80139 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -15,22 +15,28 @@ import ( func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field, required bool) error { param := field.GetParam() - if required && param.GetDefault() != nil { + defaultVal := param.GetDefault() + // don't prompt if only checking for required and has default + if required && defaultVal != nil { return nil } fmt.Fprintln(w, "Name: ", param.Name) fmt.Fprintln(w, "Prompt: ", param.Prompt) - fmt.Fprintf(w, "Input [%s]: ", displayDefault(param.GetDefault())) + fmt.Fprintf(w, "Input [%s]: ", displayDefault(defaultVal)) reader := bufio.NewReader(r) text, err := reader.ReadString('\n') if err != nil { return err } - args, err := ParseArguments(text) - if err != nil { - return err + // use default if no input given + args := []*pb.Argument{defaultVal} + if len(text) > 1 { + args, err = ParseArguments(text) + if err != nil { + return err + } } return ApplyArguments(field, args...) From 52512a441d5d3d35ae2e513bcc9863c3052b35ca Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 27 Jul 2016 12:20:06 -0700 Subject: [PATCH 76/79] removed events from tracked kube objects --- pkg/deploy/cluster.go | 6 ------ pkg/deploy/kinds.go | 1 - 2 files changed, 7 deletions(-) diff --git a/pkg/deploy/cluster.go b/pkg/deploy/cluster.go index bad5ee9..e124b66 100644 --- a/pkg/deploy/cluster.go +++ b/pkg/deploy/cluster.go @@ -288,12 +288,6 @@ func (c *KubeCluster) Deployment() (*Deployment, error) { return nil, err } } - case *kube.EventList: - for _, item := range t.Items { - if err := deployment.Add(&item); err != nil { - return nil, err - } - } case *kube.LimitRangeList: for _, item := range t.Items { if err := deployment.Add(&item); err != nil { diff --git a/pkg/deploy/kinds.go b/pkg/deploy/kinds.go index d0f3502..a7837c9 100644 --- a/pkg/deploy/kinds.go +++ b/pkg/deploy/kinds.go @@ -53,7 +53,6 @@ var kinds = map[string]KubeObject{ var resources = []string{ "configmaps", "endpoints", - "events", "limitranges", "namespaces", "persistentvolumeclaims", From a7ff60ea05b05fb8ff063626e34b59fcb7d8efe7 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Wed, 27 Jul 2016 12:22:20 -0700 Subject: [PATCH 77/79] added coercion of strings in prompts for params when we know the value is supposed to be a string; allowed the use of - in paths --- cli/param.go | 2 +- pkg/data/parameter.go | 20 +++++++++++++++----- pkg/data/sri.go | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cli/param.go b/cli/param.go index ed47150..724b8aa 100644 --- a/cli/param.go +++ b/cli/param.go @@ -78,7 +78,7 @@ func (s SpreadCli) Param() *cli.Command { // parse default value defaultInput := c.String("d") if len(defaultInput) != 0 { - args, err := data.ParseArguments(defaultInput) + args, err := data.ParseArguments(defaultInput, false) if err != nil { s.fatalf("Could not parse default value: %v", err) } else if len(args) > 1 { diff --git a/pkg/data/parameter.go b/pkg/data/parameter.go index 4a80139..62ad0bc 100644 --- a/pkg/data/parameter.go +++ b/pkg/data/parameter.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "strings" pb "rsprd.com/spread/pkg/spreadproto" ) @@ -23,7 +24,7 @@ func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field, required boo fmt.Fprintln(w, "Name: ", param.Name) fmt.Fprintln(w, "Prompt: ", param.Prompt) - fmt.Fprintf(w, "Input [%s]: ", displayDefault(defaultVal)) + fmt.Fprint(w, "Input: ", displayDefault(defaultVal)) reader := bufio.NewReader(r) text, err := reader.ReadString('\n') if err != nil { @@ -33,7 +34,8 @@ func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field, required boo // use default if no input given args := []*pb.Argument{defaultVal} if len(text) > 1 { - args, err = ParseArguments(text) + _, str := field.GetValue().(*pb.Field_Str) + args, err = ParseArguments(text[:len(text)-1], str) if err != nil { return err } @@ -43,6 +45,9 @@ func InteractiveArgs(r io.ReadCloser, w io.Writer, field *pb.Field, required boo } func displayDefault(d *pb.Argument) string { + if d.GetValue() == nil { + return "" + } var out interface{} switch val := d.GetValue().(type) { case *pb.Argument_Number: @@ -52,7 +57,7 @@ func displayDefault(d *pb.Argument) string { case *pb.Argument_Boolean: out = val.Boolean } - return fmt.Sprintf("%v", out) + return fmt.Sprintf("(%v) ", out) } // AddParamToDoc adds the given parameter to the @@ -134,11 +139,16 @@ func AddParameterFields(field *pb.Field, params map[string]*pb.Field) { } } -// ParseArguments returns arguments parsed from JSON. -func ParseArguments(in string) (args []*pb.Argument, err error) { +// ParseArguments returns arguments parsed from JSON. If coerceStr is true, will attempt to parse as string if parsing initially fails. +func ParseArguments(in string, coerceStr bool) (args []*pb.Argument, err error) { var data interface{} err = json.Unmarshal([]byte(in), &data) if err != nil { + // if coercion is requested, attempt to process input as string + if strings.HasPrefix(err.Error(), "invalid character") && coerceStr { + in = fmt.Sprintf(`"%s"`, in) + args, err = ParseArguments(in, false) + } return } diff --git a/pkg/data/sri.go b/pkg/data/sri.go index 288e488..f92df79 100644 --- a/pkg/data/sri.go +++ b/pkg/data/sri.go @@ -22,7 +22,7 @@ const ( var ( OIDRegex = regexp.MustCompile(`[^a-f0-9]+`) - PathRegex = regexp.MustCompile(`[^a-zA-Z0-9./]+`) + PathRegex = regexp.MustCompile(`[^a-zA-Z0-9./-]+`) FieldRegex = regexp.MustCompile(`[^a-zA-Z0-9./()]+`) ) From 95df1757d60486866afbea64a21d9670be07cc61 Mon Sep 17 00:00:00 2001 From: Mackenzie Burnett Date: Wed, 3 Aug 2016 16:13:26 -0700 Subject: [PATCH 78/79] Update Readme for 1.0 --- README.md | 48 ++++++++++++------------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ec3961f..ce2759c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ * Be the fastest, simplest way to deploy Docker to production * Enable collaborative deployment workflows that work well for one person or an entire team -See how we deployed Mattermost (and you can too!): +See how we versioned the cluster running our (website) (and you can too!): -

logo

+

logo

Spread is under open, active development. New features will be added regularly over the next few months - explore our [roadmap](./roadmap.md) to see what will be built next and send us pull requests for any features you’d like to see added. @@ -67,9 +67,11 @@ Here is our suggested workflow for versioning with Spread: 3. Stage an object: `spread add /` 4. Repeat until all objects have been staged 5. Commit your objects with a message: `spread commit -m "commit message"` -7. Go ahead and try out the other commands - anything not documented can be accessed using `spread git ...` +6. Set up your remote repository: `spread remote (add | remove | set-url )` +7. Push your objects to your remote repository: `spread push ` +8. Go ahead and try out the other commands - anything not documented can be accessed using `spread git ...` -Spread versioning is highly experimental for the next few weeks. If you find any bugs or have any feature requests for Spread versioning, please file an issue, and know that the format for Spread may change! +If you find any bugs or have any feature requests for Spread versioning, please file an issue! For more details on Spread commands, [see our docs](https://redspread.readme.io/docs/spread-commands). @@ -77,38 +79,9 @@ For more details on Spread commands, [see our docs](https://redspread.readme.io/ Check out our Getting Started Guide. -##Localkube +##Localkube --> Minikube -Spread makes it easy to set up and iterate with [localkube](https://github.com/redspread/localkube), a local Kubernetes cluster streamlined for rapid development. - -**Requirements:** -* [Docker](https://docs.docker.com/engine/installation/) -* [docker-machine](https://docs.docker.com/machine/install-machine/) -* [VirtualBox](https://www.virtualbox.org/wiki/Downloads) - -(Note: For Mac and Windows users, the fastest way to install everything above is [Docker Toolbox](https://www.docker.com/products/docker-toolbox).) - -**Get started:** - -1. Create a machine called dev: `docker-machine create --driver virtualbox dev` -2. Start your docker-machine: `docker-machine start dev` -3. Connect to the docker daemon: `eval "$(docker-machine env dev)"` -4. Spin up a local cluster using [localkube](http://github.com/redspread/localkube): `spread cluster start` -5. To stop the cluster: `spread cluster stop` - -**Suggested workflow:** -- `docker build` the image that you want to work with [1] -- Create Kubernetes objects that use the image build above -- Run `spread deploy .` to deploy to cluster [2] -- Iterate on your application, updating image and objects running `spread deploy .` each time you want to deploy changes -- To preview changes, grab the IP of your docker daemon with `docker-machine env `and the returned `NodePort`, then put `IP:NodePort` in your browser -- When finished, run `spread cluster stop` to stop localkube -- To remove the container entirely, run `spread cluster stop -r` - -[1] `spread` will soon integrate building ([#59](https://github.com/redspread/spread/issues/59)) -[2] Since `localkube` shares a Docker daemon with your host, there is no need to push images :) - -[See more](https://github.com/redspread/localkube) for our suggestions when developing code with `localkube`. +Spread made it easy to set up and iterate with [Localkube](https://github.com/redspread/localkube), a local Kubernetes cluster streamlined for rapid development. We have donated Localkube code to [Minikube](https://github.com/kubernetes/minikube), the official Kubernetes local development solution. It's easy to set up a local cluster with Minikube: https://github.com/kubernetes/minikube. ##What's been done so far @@ -118,17 +91,20 @@ Spread makes it easy to set up and iterate with [localkube](https://github.com/r * Updates all Kubernetes objects on a Kubernetes cluster. * Returns a public IP address, if type Load Balancer is specified. * [localkube](https://github.com/redspread/localkube): easy-to-setup local Kubernetes cluster for rapid development +* [Initial tutorial](https://redspread.readme.io/v0.1.6/docs/spread-templating-and-tutorials) for templating and parameterization ##What's being worked on now +* Template authoring +* Secret management with Spread versioning * Inner-app linking -* Parameterization * [Redspread](redspread.com) (hosted Spread repository) See more of our roadmap here! ##Future Goals * Peer-to-peer syncing between local and remote Kubernetes clusters +* Automatically spin up local and remote Kubernetes clusters with minimal user input ##FAQ From 9fbadea687c300ada7fe0f0b689533eb11d71bb0 Mon Sep 17 00:00:00 2001 From: Dan Gillespie Date: Thu, 4 Aug 2016 15:49:29 -0700 Subject: [PATCH 79/79] added printing loadbalancer hostnames (closes #140) and cleaning services of clusterIP --- cli/add.go | 20 ++++++++++++++++++++ pkg/deploy/cluster.go | 13 +++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/cli/add.go b/cli/add.go index e64e4fd..d2994a0 100644 --- a/cli/add.go +++ b/cli/add.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/codegangsta/cli" + kube "k8s.io/kubernetes/pkg/api" "rsprd.com/spread/pkg/data" "rsprd.com/spread/pkg/deploy" @@ -30,6 +31,10 @@ func (s SpreadCli) Add() *cli.Command { Name: "no-export", Usage: "don't request Kube API server to export objects", }, + cli.BoolFlag{ + Name: "clean", + Usage: "Removes fields that are known to cause issues with reproducibility", + }, }, Action: func(c *cli.Context) { // Download specified object from Kubernetes cluster @@ -60,6 +65,13 @@ func (s SpreadCli) Add() *cli.Command { s.fatalf("Could not get object from cluster: %v", err) } + if c.Bool("clean") { + err = cleanObj(kubeObj) + if err != nil { + s.fatalf("Could not get object from cluster: %v", err) + } + } + // TODO(DG): Clean this up gvk := kubeObj.GetObjectKind().GroupVersionKind() gvk.Version = "v1" @@ -84,3 +96,11 @@ func (s SpreadCli) Add() *cli.Command { }, } } + +func cleanObj(obj deploy.KubeObject) error { + switch typedObj := obj.(type) { + case *kube.Service: + typedObj.Spec.ClusterIP = "" + } + return nil +} diff --git a/pkg/deploy/cluster.go b/pkg/deploy/cluster.go index e124b66..470a6c8 100644 --- a/pkg/deploy/cluster.go +++ b/pkg/deploy/cluster.go @@ -502,9 +502,18 @@ func printLoadBalancers(client *kubecli.Client, services []KubeObject, localkube } loadBalancers := clusterVers.Status.LoadBalancer.Ingress - if len(loadBalancers) == 1 { + for _, lb := range loadBalancers { completed[s.Name] = true - fmt.Printf("Service '%s/%s' available at: \t%s\n", s.Namespace, s.Name, loadBalancers[0].IP) + + host := lb.Hostname + if len(lb.IP) != 0 { + if len(host) == 0 { + host = lb.IP + } else { + host += fmt.Sprintf(" (%s)", lb.IP) + } + } + fmt.Printf("Service '%s/%s' available at: \t%s\n", s.Namespace, s.Name, host) } } }