{"action":"created","comment":{"url":"https://api.github.com/repos/appscode/restic/pulls/comments/122156194","pull_request_review_id":44230908,"id":122156194,"diff_hunk":"@@ -0,0 +1,464 @@\n+package gs\n+\n+import (\n+\t\"context\"\n+\t\"fmt\"\n+\t\"io\"\n+\t\"os\"\n+\t\"path\"\n+\t\"restic\"\n+\t\"strings\"\n+\t\"time\"\n+\n+\t\"restic/backend\"\n+\t\"restic/errors\"\n+\n+\t\"restic/debug\"\n+\n+\tstorage \"google.golang.org/api/storage/v1\"\n+\t\"io/ioutil\"\n+\t\"golang.org/x/oauth2/google\"\n+\t\"golang.org/x/oauth2\"\n+)\n+\n+// Backend stores data on an gs endpoint.\n+type Backend struct {\n+\tservice    *storage.Service\n+\tprojectID  string\n+\tsem        *backend.Semaphore\n+\tbucketName string\n+\tprefix     string\n+\tbackend.Layout\n+}\n+\n+// make sure that *Backend implements backend.Backend\n+var _ restic.Backend = &Backend{}\n+\n+const defaultLayout = \"default\"\n+\n+func getStorageService(jsonKeyPath string) (*storage.Service, error) {\n+\n+\traw, err := ioutil.ReadFile(jsonKeyPath)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tconf, err := google.JWTConfigFromJSON(raw, storage.DevstorageReadWriteScope)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tclient := conf.Client(oauth2.NoContext)\n+\n+\tservice, err := storage.New(client)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\treturn  service, nil\n+}\n+\n+// Open opens the gs backend at bucket and region. The bucket is created if it\n+// does not exist yet.\n+func Open(cfg Config) (restic.Backend, error) {\n+\tdebug.Log(\"open, config %#v\", cfg)\n+\n+\tservice, err := getStorageService(cfg.JsonKeyPath)\n+\tif err != nil {\n+\t\treturn nil, errors.Wrap(err, \"getStorageService\")\n+\t}\n+\n+\tsem, err := backend.NewSemaphore(cfg.Connections)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tbe := &Backend{\n+\t\tservice:    service,\n+\t\tprojectID:  cfg.ProjectID,\n+\t\tsem:        sem,\n+\t\tbucketName: cfg.Bucket,\n+\t\tprefix:     cfg.Prefix,\n+\t}\n+\n+\t// TODO: CustomTransport in gcs?\n+\t//client.SetCustomTransport(backend.Transport())\n+\n+\tl, err := backend.ParseLayout(be, cfg.Layout, defaultLayout, cfg.Prefix)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tbe.Layout = l\n+\n+\t// create bucket if not exists\n+\n+\tif _, err := service.Buckets.Get(be.bucketName).Do(); err!= nil {\n+\t\t//bucket not exists, creating bucket...\n+\n+\t\tif _, err := service.Buckets.Insert(be.projectID, &storage.Bucket{Name:be.bucketName}).Do(); err!=nil {\n+\t\t\t//error creating bucket\n+\t\t\treturn nil, errors.Wrap(err, \"service.Buckets.Insert\")\n+\t\t}else{\n+\t\t\t//bucket created\n+\t\t}\n+\t}else{\n+\t\t//bucket exists\n+\t}\n+\n+\treturn be, nil\n+}\n+\n+// IsNotExist returns true if the error is caused by a not existing file.\n+func (be *Backend) IsNotExist(err error) bool {\n+\tdebug.Log(\"IsNotExist(%T, %#v)\", err, err)\n+\treturn os.IsNotExist(err)\n+}\n+\n+// Join combines path components with slashes.\n+func (be *Backend) Join(p ...string) string {\n+\treturn path.Join(p...)\n+}\n+\n+type fileInfo struct {\n+\tname    string\n+\tsize    int64\n+\tmode    os.FileMode\n+\tmodTime time.Time\n+\tisDir   bool\n+}\n+\n+func (fi fileInfo) Name() string       { return fi.name }    // base name of the file\n+func (fi fileInfo) Size() int64        { return fi.size }    // length in bytes for regular files; system-dependent for others\n+func (fi fileInfo) Mode() os.FileMode  { return fi.mode }    // file mode bits\n+func (fi fileInfo) ModTime() time.Time { return fi.modTime } // modification time\n+func (fi fileInfo) IsDir() bool        { return fi.isDir }   // abbreviation for Mode().IsDir()\n+func (fi fileInfo) Sys() interface{}   { return nil }        // underlying data source (can return nil)\n+\n+// ReadDir returns the entries for a directory.\n+func (be *Backend) ReadDir(dir string) (list []os.FileInfo, err error) {\n+\tdebug.Log(\"ReadDir(%v)\", dir)\n+\n+\t// make sure dir ends with a slash\n+\tif dir[len(dir)-1] != '/' {\n+\t\tdir += \"/\"\n+\t}\n+\n+\tif obj, err := be.service.Objects.List(be.bucketName).Prefix(dir).Delimiter(\"/\").Do(); err != nil {\n+\t\treturn nil, err\n+\t}else {\n+\t\tfor _, item := range obj.Prefixes {\n+\t\t\tentry := fileInfo{\n+\t\t\t\tname:    strings.TrimPrefix(item, dir),\n+\t\t\t\tisDir:   true,\n+\t\t\t\tmode:    os.ModeDir | 0755,\n+\t\t\t}\n+\t\t\tlist = append(list, entry)\n+\t\t}\n+\t\tfor _, item := range obj.Items {\n+\t\t\tentry := fileInfo{\n+\t\t\t\tname:    strings.TrimPrefix(item.Name, dir),\n+\t\t\t\tisDir:   false,\n+\t\t\t\tmode:    0644,\n+\t\t\t\tsize: \t int64(item.Size),\n+\t\t\t\t//modTime: item.Updated,\n+\t\t\t}\n+\t\t\tif entry.name != \"\" {\n+\t\t\t\tlist = append(list, entry)\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn list, nil\n+}\n+\n+// Location returns this backend's location (the bucket name).\n+func (be *Backend) Location() string {\n+\treturn be.Join(be.bucketName, be.prefix)\n+}\n+\n+// Path returns the path in the bucket that is used for this backend.\n+func (be *Backend) Path() string {\n+\treturn be.prefix\n+}\n+\n+// getRemainingSize returns number of bytes remaining. If it is not possible to\n+// determine the size, panic() is called.\n+func getRemainingSize(rd io.Reader) (size int64, err error) {\n+\ttype Sizer interface {\n+\t\tSize() int64\n+\t}\n+\n+\ttype Lenner interface {\n+\t\tLen() int\n+\t}\n+\n+\tif r, ok := rd.(Lenner); ok {\n+\t\tsize = int64(r.Len())\n+\t} else if r, ok := rd.(Sizer); ok {\n+\t\tsize = r.Size()\n+\t} else if f, ok := rd.(*os.File); ok {\n+\t\tfi, err := f.Stat()\n+\t\tif err != nil {\n+\t\t\treturn 0, err\n+\t\t}\n+\n+\t\tpos, err := f.Seek(0, io.SeekCurrent)\n+\t\tif err != nil {\n+\t\t\treturn 0, err\n+\t\t}\n+\n+\t\tsize = fi.Size() - pos\n+\t} else {\n+\t\tpanic(fmt.Sprintf(\"Save() got passed a reader without a method to determine the data size, type is %T\", rd))\n+\t}\n+\treturn size, nil\n+}\n+\n+// preventCloser wraps an io.Reader to run a function instead of the original Close() function.\n+type preventCloser struct {\n+\tio.Reader\n+\tf func()\n+}\n+\n+func (wr preventCloser) Close() error {\n+\twr.f()\n+\treturn nil\n+}\n+\n+// Save stores data in the backend at the handle.\n+func (be *Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) {\n+\n+\tif err := h.Valid(); err != nil {\n+\t\treturn err\n+\t}\n+\n+\tobjName := be.Filename(h)\n+\n+\t// TODO: use of size?\n+\tsize, err := getRemainingSize(rd)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tdebug.Log(\"Save %v at %v\", h, objName)\n+\n+\t// Check key does not already exist\n+\tif _, err := be.service.Objects.Get(be.bucketName, objName).Do(); err == nil {\n+\t\tdebug.Log(\"%v already exists\", h)\n+\t\treturn errors.New(\"key already exists\")\n+\t}\n+\n+\tbe.sem.GetToken()\n+\n+\t// wrap the reader so that net/http client cannot close the reader, return\n+\t// the token instead.\n+\trd = preventCloser{\n+\t\tReader: rd,\n+\t\tf: func() {\n+\t\t\tdebug.Log(\"Close()\")\n+\t\t},\n+\t}\n+\n+\tdebug.Log(\"InsertObject(%v, %v)\", be.bucketName, objName)\n+\n+\tinfo, err := be.service.Objects.Insert(be.bucketName,\n+\t\t&storage.Object{\n+\t\t\tName:objName,\n+\t\t\tSize: uint64(size),\n+\t\t}).Media(rd).Do()\n+\n+\tbe.sem.ReleaseToken()\n+\tdebug.Log(\"%v -> %v bytes, err %#v\", objName, info.Size, err)\n+\n+\treturn errors.Wrap(err, \"service.Objects.Insert\")\n+}\n+\n+// wrapReader wraps an io.ReadCloser to run an additional function on Close.\n+type wrapReader struct {\n+\tio.ReadCloser\n+\tf func()\n+}\n+\n+func (wr wrapReader) Close() error {\n+\terr := wr.ReadCloser.Close()\n+\twr.f()\n+\treturn err\n+}\n+\n+// Load returns a reader that yields the contents of the file at h at the\n+// given offset. If length is nonzero, only a portion of the file is\n+// returned. rd must be closed after use.\n+func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {\n+\tdebug.Log(\"Load %v, length %v, offset %v from %v\", h, length, offset, be.Filename(h))\n+\tif err := h.Valid(); err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif offset < 0 {\n+\t\treturn nil, errors.New(\"offset is negative\")\n+\t}\n+\n+\tif length < 0 {\n+\t\treturn nil, errors.Errorf(\"invalid length %d\", length)\n+\t}\n+\n+\tobjName := be.Filename(h)\n+\n+\tbe.sem.GetToken()\n+\n+\tvar byteRange string\n+\tif length > 0 {\n+\t\tbyteRange = fmt.Sprintf(\"bytes=%d-%d\", offset, offset+int64(length))\n+\t} else {\n+\t\tbyteRange = fmt.Sprintf(\"bytes=%d-\", offset)\n+\t}\n+\n+\treq := be.service.Objects.Get(be.bucketName, objName)\n+\t// https://cloud.google.com/storage/docs/json_api/v1/parameters#range\n+\treq.Header().Set(\"Range\", byteRange)\n+\tres, err := req.Download()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tcloseRd := wrapReader{\n+\t\tReadCloser: res.Body,\n+\t\tf: func() {\n+\t\t\tdebug.Log(\"Close()\")\n+\t\t\tbe.sem.ReleaseToken()\n+\t\t},\n+\t}\n+\n+\treturn closeRd, err\n+}\n+\n+// Stat returns information about a blob.\n+func (be *Backend) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInfo, err error) {\n+\tdebug.Log(\"%v\", h)\n+\n+\tobjName := be.Filename(h)\n+\n+\tobj, err := be.service.Objects.Get(be.bucketName, objName).Do()\n+\tif err != nil {\n+\t\tdebug.Log(\"GetObject() err %v\", err)\n+\t\treturn restic.FileInfo{}, errors.Wrap(err, \"service.Objects.Get\")\n+\t}\n+\n+\treturn restic.FileInfo{Size: int64(obj.Size)}, nil\n+}\n+\n+// Test returns true if a blob of the given type and name exists in the backend.\n+func (be *Backend) Test(ctx context.Context, h restic.Handle) (bool, error) {\n+\tfound := false\n+\tobjName := be.Filename(h)\n+","path":"src/restic/backend/gs/gs.go","position":355,"original_position":355,"commit_id":"ee133eafa0eb78a9e36acd03fade6817b49028ce","original_commit_id":"ee133eafa0eb78a9e36acd03fade6817b49028ce","user":{"login":"sadlil","id":5103096,"avatar_url":"https://avatars3.githubusercontent.com/u/5103096?v=3","gravatar_id":"","url":"https://api.github.com/users/sadlil","html_url":"https://github.com/sadlil","followers_url":"https://api.github.com/users/sadlil/followers","following_url":"https://api.github.com/users/sadlil/following{/other_user}","gists_url":"https://api.github.com/users/sadlil/gists{/gist_id}","starred_url":"https://api.github.com/users/sadlil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sadlil/subscriptions","organizations_url":"https://api.github.com/users/sadlil/orgs","repos_url":"https://api.github.com/users/sadlil/repos","events_url":"https://api.github.com/users/sadlil/events{/privacy}","received_events_url":"https://api.github.com/users/sadlil/received_events","type":"User","site_admin":false},"body":"bl","created_at":"2017-06-15T09:11:21Z","updated_at":"2017-06-15T09:20:34Z","html_url":"https://github.com/appscode/restic/pull/2#discussion_r122156194","pull_request_url":"https://api.github.com/repos/appscode/restic/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/appscode/restic/pulls/comments/122156194"},"html":{"href":"https://github.com/appscode/restic/pull/2#discussion_r122156194"},"pull_request":{"href":"https://api.github.com/repos/appscode/restic/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/appscode/restic/pulls/2","id":125760466,"html_url":"https://github.com/appscode/restic/pull/2","diff_url":"https://github.com/appscode/restic/pull/2.diff","patch_url":"https://github.com/appscode/restic/pull/2.patch","issue_url":"https://api.github.com/repos/appscode/restic/issues/2","number":2,"state":"open","locked":false,"title":"add gcs support","user":{"login":"diptadas","id":16899461,"avatar_url":"https://avatars1.githubusercontent.com/u/16899461?v=3","gravatar_id":"","url":"https://api.github.com/users/diptadas","html_url":"https://github.com/diptadas","followers_url":"https://api.github.com/users/diptadas/followers","following_url":"https://api.github.com/users/diptadas/following{/other_user}","gists_url":"https://api.github.com/users/diptadas/gists{/gist_id}","starred_url":"https://api.github.com/users/diptadas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/diptadas/subscriptions","organizations_url":"https://api.github.com/users/diptadas/orgs","repos_url":"https://api.github.com/users/diptadas/repos","events_url":"https://api.github.com/users/diptadas/events{/privacy}","received_events_url":"https://api.github.com/users/diptadas/received_events","type":"User","site_admin":false},"body":"Environment variables:\r\nGOOGLE_PROJECT_ID=<MY_PROJECT_ID>\r\nGOOGLE_APPLICATION_CREDENTIALS=<PATH_TO_JSON_FILE>\r\n\r\nInit repository: \r\nrestic -r gs:googleapis.com/bucket-name init\r\n","created_at":"2017-06-15T07:45:08Z","updated_at":"2017-06-15T09:20:34Z","closed_at":null,"merged_at":null,"merge_commit_sha":"fa48ea3aa6dca1250c77acacef7ebb9650acea8d","assignee":null,"assignees":[],"requested_reviewers":[{"login":"TamalSaha","id":94814,"avatar_url":"https://avatars2.githubusercontent.com/u/94814?v=3","gravatar_id":"","url":"https://api.github.com/users/TamalSaha","html_url":"https://github.com/TamalSaha","followers_url":"https://api.github.com/users/TamalSaha/followers","following_url":"https://api.github.com/users/TamalSaha/following{/other_user}","gists_url":"https://api.github.com/users/TamalSaha/gists{/gist_id}","starred_url":"https://api.github.com/users/TamalSaha/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TamalSaha/subscriptions","organizations_url":"https://api.github.com/users/TamalSaha/orgs","repos_url":"https://api.github.com/users/TamalSaha/repos","events_url":"https://api.github.com/users/TamalSaha/events{/privacy}","received_events_url":"https://api.github.com/users/TamalSaha/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/appscode/restic/pulls/2/commits","review_comments_url":"https://api.github.com/repos/appscode/restic/pulls/2/comments","review_comment_url":"https://api.github.com/repos/appscode/restic/pulls/comments{/number}","comments_url":"https://api.github.com/repos/appscode/restic/issues/2/comments","statuses_url":"https://api.github.com/repos/appscode/restic/statuses/ee133eafa0eb78a9e36acd03fade6817b49028ce","head":{"label":"appscode:gcs-support","ref":"gcs-support","sha":"ee133eafa0eb78a9e36acd03fade6817b49028ce","user":{"login":"appscode","id":13395825,"avatar_url":"https://avatars1.githubusercontent.com/u/13395825?v=3","gravatar_id":"","url":"https://api.github.com/users/appscode","html_url":"https://github.com/appscode","followers_url":"https://api.github.com/users/appscode/followers","following_url":"https://api.github.com/users/appscode/following{/other_user}","gists_url":"https://api.github.com/users/appscode/gists{/gist_id}","starred_url":"https://api.github.com/users/appscode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/appscode/subscriptions","organizations_url":"https://api.github.com/users/appscode/orgs","repos_url":"https://api.github.com/users/appscode/repos","events_url":"https://api.github.com/users/appscode/events{/privacy}","received_events_url":"https://api.github.com/users/appscode/received_events","type":"Organization","site_admin":false},"repo":{"id":84284407,"name":"restic","full_name":"appscode/restic","owner":{"login":"appscode","id":13395825,"avatar_url":"https://avatars1.githubusercontent.com/u/13395825?v=3","gravatar_id":"","url":"https://api.github.com/users/appscode","html_url":"https://github.com/appscode","followers_url":"https://api.github.com/users/appscode/followers","following_url":"https://api.github.com/users/appscode/following{/other_user}","gists_url":"https://api.github.com/users/appscode/gists{/gist_id}","starred_url":"https://api.github.com/users/appscode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/appscode/subscriptions","organizations_url":"https://api.github.com/users/appscode/orgs","repos_url":"https://api.github.com/users/appscode/repos","events_url":"https://api.github.com/users/appscode/events{/privacy}","received_events_url":"https://api.github.com/users/appscode/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/appscode/restic","description":"restic backup program","fork":true,"url":"https://api.github.com/repos/appscode/restic","forks_url":"https://api.github.com/repos/appscode/restic/forks","keys_url":"https://api.github.com/repos/appscode/restic/keys{/key_id}","collaborators_url":"https://api.github.com/repos/appscode/restic/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/appscode/restic/teams","hooks_url":"https://api.github.com/repos/appscode/restic/hooks","issue_events_url":"https://api.github.com/repos/appscode/restic/issues/events{/number}","events_url":"https://api.github.com/repos/appscode/restic/events","assignees_url":"https://api.github.com/repos/appscode/restic/assignees{/user}","branches_url":"https://api.github.com/repos/appscode/restic/branches{/branch}","tags_url":"https://api.github.com/repos/appscode/restic/tags","blobs_url":"https://api.github.com/repos/appscode/restic/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/appscode/restic/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/appscode/restic/git/refs{/sha}","trees_url":"https://api.github.com/repos/appscode/restic/git/trees{/sha}","statuses_url":"https://api.github.com/repos/appscode/restic/statuses/{sha}","languages_url":"https://api.github.com/repos/appscode/restic/languages","stargazers_url":"https://api.github.com/repos/appscode/restic/stargazers","contributors_url":"https://api.github.com/repos/appscode/restic/contributors","subscribers_url":"https://api.github.com/repos/appscode/restic/subscribers","subscription_url":"https://api.github.com/repos/appscode/restic/subscription","commits_url":"https://api.github.com/repos/appscode/restic/commits{/sha}","git_commits_url":"https://api.github.com/repos/appscode/restic/git/commits{/sha}","comments_url":"https://api.github.com/repos/appscode/restic/comments{/number}","issue_comment_url":"https://api.github.com/repos/appscode/restic/issues/comments{/number}","contents_url":"https://api.github.com/repos/appscode/restic/contents/{+path}","compare_url":"https://api.github.com/repos/appscode/restic/compare/{base}...{head}","merges_url":"https://api.github.com/repos/appscode/restic/merges","archive_url":"https://api.github.com/repos/appscode/restic/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/appscode/restic/downloads","issues_url":"https://api.github.com/repos/appscode/restic/issues{/number}","pulls_url":"https://api.github.com/repos/appscode/restic/pulls{/number}","milestones_url":"https://api.github.com/repos/appscode/restic/milestones{/number}","notifications_url":"https://api.github.com/repos/appscode/restic/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/appscode/restic/labels{/name}","releases_url":"https://api.github.com/repos/appscode/restic/releases{/id}","deployments_url":"https://api.github.com/repos/appscode/restic/deployments","created_at":"2017-03-08T06:02:15Z","updated_at":"2017-03-08T06:02:17Z","pushed_at":"2017-06-15T08:27:31Z","git_url":"git://github.com/appscode/restic.git","ssh_url":"git@github.com:appscode/restic.git","clone_url":"https://github.com/appscode/restic.git","svn_url":"https://github.com/appscode/restic","homepage":"https://restic.github.io/","size":29891,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"appscode:master","ref":"master","sha":"a6f157f346adc07be7ddf44e6e9bc5f8daa683e7","user":{"login":"appscode","id":13395825,"avatar_url":"https://avatars1.githubusercontent.com/u/13395825?v=3","gravatar_id":"","url":"https://api.github.com/users/appscode","html_url":"https://github.com/appscode","followers_url":"https://api.github.com/users/appscode/followers","following_url":"https://api.github.com/users/appscode/following{/other_user}","gists_url":"https://api.github.com/users/appscode/gists{/gist_id}","starred_url":"https://api.github.com/users/appscode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/appscode/subscriptions","organizations_url":"https://api.github.com/users/appscode/orgs","repos_url":"https://api.github.com/users/appscode/repos","events_url":"https://api.github.com/users/appscode/events{/privacy}","received_events_url":"https://api.github.com/users/appscode/received_events","type":"Organization","site_admin":false},"repo":{"id":84284407,"name":"restic","full_name":"appscode/restic","owner":{"login":"appscode","id":13395825,"avatar_url":"https://avatars1.githubusercontent.com/u/13395825?v=3","gravatar_id":"","url":"https://api.github.com/users/appscode","html_url":"https://github.com/appscode","followers_url":"https://api.github.com/users/appscode/followers","following_url":"https://api.github.com/users/appscode/following{/other_user}","gists_url":"https://api.github.com/users/appscode/gists{/gist_id}","starred_url":"https://api.github.com/users/appscode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/appscode/subscriptions","organizations_url":"https://api.github.com/users/appscode/orgs","repos_url":"https://api.github.com/users/appscode/repos","events_url":"https://api.github.com/users/appscode/events{/privacy}","received_events_url":"https://api.github.com/users/appscode/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/appscode/restic","description":"restic backup program","fork":true,"url":"https://api.github.com/repos/appscode/restic","forks_url":"https://api.github.com/repos/appscode/restic/forks","keys_url":"https://api.github.com/repos/appscode/restic/keys{/key_id}","collaborators_url":"https://api.github.com/repos/appscode/restic/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/appscode/restic/teams","hooks_url":"https://api.github.com/repos/appscode/restic/hooks","issue_events_url":"https://api.github.com/repos/appscode/restic/issues/events{/number}","events_url":"https://api.github.com/repos/appscode/restic/events","assignees_url":"https://api.github.com/repos/appscode/restic/assignees{/user}","branches_url":"https://api.github.com/repos/appscode/restic/branches{/branch}","tags_url":"https://api.github.com/repos/appscode/restic/tags","blobs_url":"https://api.github.com/repos/appscode/restic/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/appscode/restic/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/appscode/restic/git/refs{/sha}","trees_url":"https://api.github.com/repos/appscode/restic/git/trees{/sha}","statuses_url":"https://api.github.com/repos/appscode/restic/statuses/{sha}","languages_url":"https://api.github.com/repos/appscode/restic/languages","stargazers_url":"https://api.github.com/repos/appscode/restic/stargazers","contributors_url":"https://api.github.com/repos/appscode/restic/contributors","subscribers_url":"https://api.github.com/repos/appscode/restic/subscribers","subscription_url":"https://api.github.com/repos/appscode/restic/subscription","commits_url":"https://api.github.com/repos/appscode/restic/commits{/sha}","git_commits_url":"https://api.github.com/repos/appscode/restic/git/commits{/sha}","comments_url":"https://api.github.com/repos/appscode/restic/comments{/number}","issue_comment_url":"https://api.github.com/repos/appscode/restic/issues/comments{/number}","contents_url":"https://api.github.com/repos/appscode/restic/contents/{+path}","compare_url":"https://api.github.com/repos/appscode/restic/compare/{base}...{head}","merges_url":"https://api.github.com/repos/appscode/restic/merges","archive_url":"https://api.github.com/repos/appscode/restic/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/appscode/restic/downloads","issues_url":"https://api.github.com/repos/appscode/restic/issues{/number}","pulls_url":"https://api.github.com/repos/appscode/restic/pulls{/number}","milestones_url":"https://api.github.com/repos/appscode/restic/milestones{/number}","notifications_url":"https://api.github.com/repos/appscode/restic/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/appscode/restic/labels{/name}","releases_url":"https://api.github.com/repos/appscode/restic/releases{/id}","deployments_url":"https://api.github.com/repos/appscode/restic/deployments","created_at":"2017-03-08T06:02:15Z","updated_at":"2017-03-08T06:02:17Z","pushed_at":"2017-06-15T08:27:31Z","git_url":"git://github.com/appscode/restic.git","ssh_url":"git@github.com:appscode/restic.git","clone_url":"https://github.com/appscode/restic.git","svn_url":"https://github.com/appscode/restic","homepage":"https://restic.github.io/","size":29891,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/appscode/restic/pulls/2"},"html":{"href":"https://github.com/appscode/restic/pull/2"},"issue":{"href":"https://api.github.com/repos/appscode/restic/issues/2"},"comments":{"href":"https://api.github.com/repos/appscode/restic/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/appscode/restic/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/appscode/restic/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/appscode/restic/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/appscode/restic/statuses/ee133eafa0eb78a9e36acd03fade6817b49028ce"}}}}
{"actor":{"display_login":"sadlil"}}