{"action":"created","comment":{"url":"https://api.github.com/repos/keybase/kbfs/pulls/comments/67092566","id":67092566,"diff_hunk":"@@ -0,0 +1,706 @@\n+// Copyright 2016 Keybase Inc. All rights reserved.\n+// Use of this source code is governed by a BSD\n+// license that can be found in the LICENSE file.\n+\n+package libkbfs\n+\n+import (\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"io/ioutil\"\n+\t\"os\"\n+\t\"path/filepath\"\n+\t\"reflect\"\n+\t\"strconv\"\n+\t\"sync\"\n+)\n+\n+// bserverTlfJournal stores an ordered list of BlockServer mutating\n+// operations for a single TLF, along with associated block data, in\n+// flat files in a directory on disk.\n+//\n+// The directory layout looks like:\n+//\n+// dir/journal/EARLIEST\n+// dir/journal/LATEST\n+// dir/journal/0...000\n+// dir/journal/0...001\n+// dir/journal/0...fff\n+// dir/blocks/0100/0...01/data\n+// dir/blocks/0100/0...01/key_server_half\n+// ...\n+// dir/blocks/01ff/f...ff/data\n+// dir/blocks/01ff/f...ff/key_server_half\n+//\n+// Each file in dir/journal is named with an ordinal and contains the\n+// mutating operation and arguments for a single operation, except for\n+// block data. The files EARLIEST and LATEST point to the earliest and\n+// latest valid ordinal, respectively.\n+//\n+// The block data is stored separately in dir/blocks. Each block has\n+// its own subdirectory with its ID as a name.  The block\n+// subdirectories are splayed over (# of possible hash types) * 256\n+// subdirectories -- one byte for the hash type (currently only one)\n+// plus the first byte of the hash data -- using the first four\n+// characters of the name to keep the number of directories in dir\n+// itself to a manageable number, similar to git. Each block directory\n+// has data, which is the raw block data that should hash to the block\n+// ID, and key_server_half, which contains the raw data for the\n+// associated key server half.\n+//\n+// TODO: Do all high-level operations atomically on the file-system\n+// level.\n+//\n+// TODO: Make IO ops cancellable.\n+//\n+// TODO: Add a mode which doesn't assume that this is the only storage\n+// for TLF data, i.e. that doesn't remove files when the (known)\n+// refcount drops to zero, etc.\n+type bserverTlfJournal struct {\n+\tcodec  Codec\n+\tcrypto cryptoPure\n+\tdir    string\n+\n+\t// Protects any IO operations in dir or any of its children,\n+\t// as well as refs and isShutdown.\n+\t//\n+\t// TODO: Consider using https://github.com/pkg/singlefile\n+\t// instead.\n+\tlock       sync.RWMutex\n+\trefs       map[BlockID]blockRefMap\n+\tisShutdown bool\n+}\n+\n+// makeBserverTlfJournal returns a new bserverTlfJournal for the given\n+// directory. Any existing journal entries are read.\n+func makeBserverTlfJournal(codec Codec, crypto cryptoPure, dir string) (\n+\t*bserverTlfJournal, error) {\n+\tbserver := &bserverTlfJournal{\n+\t\tcodec:  codec,\n+\t\tcrypto: crypto,\n+\t\tdir:    dir,\n+\t}\n+\n+\t// Locking here is not strictly necessary, but do it anyway\n+\t// for consistency.\n+\tbserver.lock.Lock()\n+\tdefer bserver.lock.Unlock()\n+\trefs, err := bserver.readJournalLocked()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tbserver.refs = refs\n+\treturn bserver, nil\n+}\n+\n+// journalOrdinal is the ordinal used for naming journal entries.\n+//\n+// TODO: Incorporate metadata revision numbers.\n+type journalOrdinal uint64\n+\n+func makeJournalOrdinal(s string) (journalOrdinal, error) {\n+\tif len(s) != 16 {\n+\t\treturn 0, fmt.Errorf(\"invalid journal ordinal %q\", s)\n+\t}\n+\tu, err := strconv.ParseUint(s, 16, 64)\n+\tif err != nil {\n+\t\treturn 0, err\n+\t}\n+\treturn journalOrdinal(u), nil\n+}\n+\n+func (o journalOrdinal) String() string {\n+\treturn fmt.Sprintf(\"%016x\", uint64(o))\n+}\n+\n+// The functions below are for building various paths for the journal.\n+\n+func (s *bserverTlfJournal) journalPath() string {\n+\treturn filepath.Join(s.dir, \"journal\")\n+}\n+\n+func (s *bserverTlfJournal) earliestPath() string {\n+\treturn filepath.Join(s.journalPath(), \"EARLIEST\")\n+}\n+\n+func (s *bserverTlfJournal) latestPath() string {\n+\treturn filepath.Join(s.journalPath(), \"LATEST\")\n+}\n+\n+func (s *bserverTlfJournal) journalEntryPath(o journalOrdinal) string {\n+\treturn filepath.Join(s.journalPath(), o.String())\n+}\n+\n+func (s *bserverTlfJournal) blocksPath() string {\n+\treturn filepath.Join(s.dir, \"blocks\")\n+}\n+\n+func (s *bserverTlfJournal) blockPath(id BlockID) string {\n+\tidStr := id.String()\n+\treturn filepath.Join(s.blocksPath(), idStr[:4], idStr[4:])\n+}\n+\n+func (s *bserverTlfJournal) blockDataPath(id BlockID) string {\n+\treturn filepath.Join(s.blockPath(id), \"data\")\n+}\n+\n+func (s *bserverTlfJournal) keyServerHalfPath(id BlockID) string {\n+\treturn filepath.Join(s.blockPath(id), \"key_server_half\")\n+}\n+\n+// The functions below are for getting and setting the earliest and\n+// latest ordinals.\n+\n+func (s *bserverTlfJournal) readOrdinalLocked(path string) (\n+\tjournalOrdinal, error) {\n+\tbuf, err := ioutil.ReadFile(path)\n+\tif err != nil {\n+\t\treturn 0, err\n+\t}\n+\treturn makeJournalOrdinal(string(buf))\n+}\n+\n+func (s *bserverTlfJournal) writeOrdinalLocked(\n+\tpath string, o journalOrdinal) error {\n+\treturn ioutil.WriteFile(path, []byte(o.String()), 0600)\n+}\n+\n+func (s *bserverTlfJournal) readEarliestOrdinalLocked() (\n+\tjournalOrdinal, error) {\n+\treturn s.readOrdinalLocked(s.earliestPath())\n+}\n+\n+func (s *bserverTlfJournal) writeEarliestOrdinalLocked(o journalOrdinal) error {\n+\treturn s.writeOrdinalLocked(s.earliestPath(), o)\n+}\n+\n+func (s *bserverTlfJournal) readLatestOrdinalLocked() (journalOrdinal, error) {\n+\treturn s.readOrdinalLocked(s.latestPath())\n+}\n+\n+func (s *bserverTlfJournal) writeLatestOrdinalLocked(o journalOrdinal) error {\n+\treturn s.writeOrdinalLocked(s.latestPath(), o)\n+}\n+\n+const (\n+\tblockPutOp    = \"blockPut\"\n+\taddRefOp      = \"addReference\"\n+\tremoveRefsOp  = \"removeReferences\"\n+\tarchiveRefsOp = \"archiveReferences\"\n+)\n+\n+// A bserverJournalEntry is just the name of the operation and the\n+// associated block ID and contexts. Fields are exported only for\n+// serialization.\n+type bserverJournalEntry struct {\n+\t// Must be one of the four ops above.\n+\tOp string\n+\tID BlockID\n+\t// Must have exactly one entry for blockPutOp and addRefOp,\n+\t// and at least one for removeRefsOp and archiveRefsOp.\n+\tContexts []BlockContext\n+}\n+\n+// The functions below are for reading and writing journal entries.\n+\n+func (s *bserverTlfJournal) readJournalEntryLocked(o journalOrdinal) (\n+\tbserverJournalEntry, error) {\n+\tp := s.journalEntryPath(o)\n+\tbuf, err := ioutil.ReadFile(p)\n+\tif err != nil {\n+\t\treturn bserverJournalEntry{}, err\n+\t}\n+\n+\tvar e bserverJournalEntry\n+\terr = s.codec.Decode(buf, &e)\n+\tif err != nil {\n+\t\treturn bserverJournalEntry{}, err\n+\t}\n+\n+\treturn e, nil\n+}\n+\n+// readJournalLocked reads the journal and returns a map of all the\n+// block references in the journal.\n+func (s *bserverTlfJournal) readJournalLocked() (\n+\tmap[BlockID]blockRefMap, error) {\n+\trefs := make(map[BlockID]blockRefMap)\n+\n+\tfirst, err := s.readEarliestOrdinalLocked()\n+\tif os.IsNotExist(err) {\n+\t\treturn refs, nil\n+\t} else if err != nil {\n+\t\treturn nil, err\n+\t}\n+\tlast, err := s.readLatestOrdinalLocked()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tfor i := first; i <= last; i++ {\n+\t\te, err := s.readJournalEntryLocked(i)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\n+\t\tblockRefs := refs[e.ID]\n+\t\tif blockRefs == nil {\n+\t\t\tblockRefs = make(blockRefMap)\n+\t\t\trefs[e.ID] = blockRefs\n+\t\t}\n+\n+\t\tswitch e.Op {\n+\t\tcase \"put\", \"addReference\":","path":"libkbfs/bserver_tlf_journal.go","position":null,"original_position":254,"commit_id":"a20cb69e05617388d49a4778de53839ed6badf44","original_commit_id":"1860fac1e433a532a7478f6e427ccac84499606f","user":{"login":"akalin-keybase","id":13270622,"avatar_url":"https://avatars.githubusercontent.com/u/13270622?v=3","gravatar_id":"","url":"https://api.github.com/users/akalin-keybase","html_url":"https://github.com/akalin-keybase","followers_url":"https://api.github.com/users/akalin-keybase/followers","following_url":"https://api.github.com/users/akalin-keybase/following{/other_user}","gists_url":"https://api.github.com/users/akalin-keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/akalin-keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akalin-keybase/subscriptions","organizations_url":"https://api.github.com/users/akalin-keybase/orgs","repos_url":"https://api.github.com/users/akalin-keybase/repos","events_url":"https://api.github.com/users/akalin-keybase/events{/privacy}","received_events_url":"https://api.github.com/users/akalin-keybase/received_events","type":"User","site_admin":false},"body":"Oops, forgot to do that. Done.","created_at":"2016-06-15T02:53:52Z","updated_at":"2016-06-15T02:53:52Z","html_url":"https://github.com/keybase/kbfs/pull/39#discussion_r67092566","pull_request_url":"https://api.github.com/repos/keybase/kbfs/pulls/39","_links":{"self":{"href":"https://api.github.com/repos/keybase/kbfs/pulls/comments/67092566"},"html":{"href":"https://github.com/keybase/kbfs/pull/39#discussion_r67092566"},"pull_request":{"href":"https://api.github.com/repos/keybase/kbfs/pulls/39"}}},"pull_request":{"url":"https://api.github.com/repos/keybase/kbfs/pulls/39","id":73647104,"html_url":"https://github.com/keybase/kbfs/pull/39","diff_url":"https://github.com/keybase/kbfs/pull/39.diff","patch_url":"https://github.com/keybase/kbfs/pull/39.patch","issue_url":"https://api.github.com/repos/keybase/kbfs/issues/39","number":39,"state":"open","locked":false,"title":"[KBFS-1097] Change bserverTlfStorage to bserverTlfJournal","user":{"login":"akalin-keybase","id":13270622,"avatar_url":"https://avatars.githubusercontent.com/u/13270622?v=3","gravatar_id":"","url":"https://api.github.com/users/akalin-keybase","html_url":"https://github.com/akalin-keybase","followers_url":"https://api.github.com/users/akalin-keybase/followers","following_url":"https://api.github.com/users/akalin-keybase/following{/other_user}","gists_url":"https://api.github.com/users/akalin-keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/akalin-keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akalin-keybase/subscriptions","organizations_url":"https://api.github.com/users/akalin-keybase/orgs","repos_url":"https://api.github.com/users/akalin-keybase/repos","events_url":"https://api.github.com/users/akalin-keybase/events{/privacy}","received_events_url":"https://api.github.com/users/akalin-keybase/received_events","type":"User","site_admin":false},"body":"Store operations in a journal instead of an on-disk\nreference map.","created_at":"2016-06-13T21:33:50Z","updated_at":"2016-06-15T02:53:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bc9bf23cdc255cd97365ad8b61018ae0bfe6a41e","assignee":{"login":"strib","id":8516691,"avatar_url":"https://avatars.githubusercontent.com/u/8516691?v=3","gravatar_id":"","url":"https://api.github.com/users/strib","html_url":"https://github.com/strib","followers_url":"https://api.github.com/users/strib/followers","following_url":"https://api.github.com/users/strib/following{/other_user}","gists_url":"https://api.github.com/users/strib/gists{/gist_id}","starred_url":"https://api.github.com/users/strib/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/strib/subscriptions","organizations_url":"https://api.github.com/users/strib/orgs","repos_url":"https://api.github.com/users/strib/repos","events_url":"https://api.github.com/users/strib/events{/privacy}","received_events_url":"https://api.github.com/users/strib/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/keybase/kbfs/pulls/39/commits","review_comments_url":"https://api.github.com/repos/keybase/kbfs/pulls/39/comments","review_comment_url":"https://api.github.com/repos/keybase/kbfs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/keybase/kbfs/issues/39/comments","statuses_url":"https://api.github.com/repos/keybase/kbfs/statuses/a20cb69e05617388d49a4778de53839ed6badf44","head":{"label":"keybase:akalin/bserver-journal","ref":"akalin/bserver-journal","sha":"a20cb69e05617388d49a4778de53839ed6badf44","user":{"login":"keybase","id":5400834,"avatar_url":"https://avatars.githubusercontent.com/u/5400834?v=3","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"repo":{"id":59787605,"name":"kbfs","full_name":"keybase/kbfs","owner":{"login":"keybase","id":5400834,"avatar_url":"https://avatars.githubusercontent.com/u/5400834?v=3","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/keybase/kbfs","description":"Keybase Filesystem (KBFS)","fork":false,"url":"https://api.github.com/repos/keybase/kbfs","forks_url":"https://api.github.com/repos/keybase/kbfs/forks","keys_url":"https://api.github.com/repos/keybase/kbfs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/keybase/kbfs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/keybase/kbfs/teams","hooks_url":"https://api.github.com/repos/keybase/kbfs/hooks","issue_events_url":"https://api.github.com/repos/keybase/kbfs/issues/events{/number}","events_url":"https://api.github.com/repos/keybase/kbfs/events","assignees_url":"https://api.github.com/repos/keybase/kbfs/assignees{/user}","branches_url":"https://api.github.com/repos/keybase/kbfs/branches{/branch}","tags_url":"https://api.github.com/repos/keybase/kbfs/tags","blobs_url":"https://api.github.com/repos/keybase/kbfs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/keybase/kbfs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/keybase/kbfs/git/refs{/sha}","trees_url":"https://api.github.com/repos/keybase/kbfs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/keybase/kbfs/statuses/{sha}","languages_url":"https://api.github.com/repos/keybase/kbfs/languages","stargazers_url":"https://api.github.com/repos/keybase/kbfs/stargazers","contributors_url":"https://api.github.com/repos/keybase/kbfs/contributors","subscribers_url":"https://api.github.com/repos/keybase/kbfs/subscribers","subscription_url":"https://api.github.com/repos/keybase/kbfs/subscription","commits_url":"https://api.github.com/repos/keybase/kbfs/commits{/sha}","git_commits_url":"https://api.github.com/repos/keybase/kbfs/git/commits{/sha}","comments_url":"https://api.github.com/repos/keybase/kbfs/comments{/number}","issue_comment_url":"https://api.github.com/repos/keybase/kbfs/issues/comments{/number}","contents_url":"https://api.github.com/repos/keybase/kbfs/contents/{+path}","compare_url":"https://api.github.com/repos/keybase/kbfs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/keybase/kbfs/merges","archive_url":"https://api.github.com/repos/keybase/kbfs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/keybase/kbfs/downloads","issues_url":"https://api.github.com/repos/keybase/kbfs/issues{/number}","pulls_url":"https://api.github.com/repos/keybase/kbfs/pulls{/number}","milestones_url":"https://api.github.com/repos/keybase/kbfs/milestones{/number}","notifications_url":"https://api.github.com/repos/keybase/kbfs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/keybase/kbfs/labels{/name}","releases_url":"https://api.github.com/repos/keybase/kbfs/releases{/id}","deployments_url":"https://api.github.com/repos/keybase/kbfs/deployments","created_at":"2016-05-26T22:37:42Z","updated_at":"2016-06-15T02:20:59Z","pushed_at":"2016-06-15T02:24:40Z","git_url":"git://github.com/keybase/kbfs.git","ssh_url":"git@github.com:keybase/kbfs.git","clone_url":"https://github.com/keybase/kbfs.git","svn_url":"https://github.com/keybase/kbfs","homepage":"https://keybase.io/docs/kbfs","size":16860,"stargazers_count":21,"watchers_count":21,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":5,"forks":2,"open_issues":5,"watchers":21,"default_branch":"master"}},"base":{"label":"keybase:master","ref":"master","sha":"2dbe76f657e3c9aaf0eb401b3bd04c2ffee58640","user":{"login":"keybase","id":5400834,"avatar_url":"https://avatars.githubusercontent.com/u/5400834?v=3","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"repo":{"id":59787605,"name":"kbfs","full_name":"keybase/kbfs","owner":{"login":"keybase","id":5400834,"avatar_url":"https://avatars.githubusercontent.com/u/5400834?v=3","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/keybase/kbfs","description":"Keybase Filesystem (KBFS)","fork":false,"url":"https://api.github.com/repos/keybase/kbfs","forks_url":"https://api.github.com/repos/keybase/kbfs/forks","keys_url":"https://api.github.com/repos/keybase/kbfs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/keybase/kbfs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/keybase/kbfs/teams","hooks_url":"https://api.github.com/repos/keybase/kbfs/hooks","issue_events_url":"https://api.github.com/repos/keybase/kbfs/issues/events{/number}","events_url":"https://api.github.com/repos/keybase/kbfs/events","assignees_url":"https://api.github.com/repos/keybase/kbfs/assignees{/user}","branches_url":"https://api.github.com/repos/keybase/kbfs/branches{/branch}","tags_url":"https://api.github.com/repos/keybase/kbfs/tags","blobs_url":"https://api.github.com/repos/keybase/kbfs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/keybase/kbfs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/keybase/kbfs/git/refs{/sha}","trees_url":"https://api.github.com/repos/keybase/kbfs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/keybase/kbfs/statuses/{sha}","languages_url":"https://api.github.com/repos/keybase/kbfs/languages","stargazers_url":"https://api.github.com/repos/keybase/kbfs/stargazers","contributors_url":"https://api.github.com/repos/keybase/kbfs/contributors","subscribers_url":"https://api.github.com/repos/keybase/kbfs/subscribers","subscription_url":"https://api.github.com/repos/keybase/kbfs/subscription","commits_url":"https://api.github.com/repos/keybase/kbfs/commits{/sha}","git_commits_url":"https://api.github.com/repos/keybase/kbfs/git/commits{/sha}","comments_url":"https://api.github.com/repos/keybase/kbfs/comments{/number}","issue_comment_url":"https://api.github.com/repos/keybase/kbfs/issues/comments{/number}","contents_url":"https://api.github.com/repos/keybase/kbfs/contents/{+path}","compare_url":"https://api.github.com/repos/keybase/kbfs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/keybase/kbfs/merges","archive_url":"https://api.github.com/repos/keybase/kbfs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/keybase/kbfs/downloads","issues_url":"https://api.github.com/repos/keybase/kbfs/issues{/number}","pulls_url":"https://api.github.com/repos/keybase/kbfs/pulls{/number}","milestones_url":"https://api.github.com/repos/keybase/kbfs/milestones{/number}","notifications_url":"https://api.github.com/repos/keybase/kbfs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/keybase/kbfs/labels{/name}","releases_url":"https://api.github.com/repos/keybase/kbfs/releases{/id}","deployments_url":"https://api.github.com/repos/keybase/kbfs/deployments","created_at":"2016-05-26T22:37:42Z","updated_at":"2016-06-15T02:20:59Z","pushed_at":"2016-06-15T02:24:40Z","git_url":"git://github.com/keybase/kbfs.git","ssh_url":"git@github.com:keybase/kbfs.git","clone_url":"https://github.com/keybase/kbfs.git","svn_url":"https://github.com/keybase/kbfs","homepage":"https://keybase.io/docs/kbfs","size":16860,"stargazers_count":21,"watchers_count":21,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":5,"forks":2,"open_issues":5,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/keybase/kbfs/pulls/39"},"html":{"href":"https://github.com/keybase/kbfs/pull/39"},"issue":{"href":"https://api.github.com/repos/keybase/kbfs/issues/39"},"comments":{"href":"https://api.github.com/repos/keybase/kbfs/issues/39/comments"},"review_comments":{"href":"https://api.github.com/repos/keybase/kbfs/pulls/39/comments"},"review_comment":{"href":"https://api.github.com/repos/keybase/kbfs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/keybase/kbfs/pulls/39/commits"},"statuses":{"href":"https://api.github.com/repos/keybase/kbfs/statuses/a20cb69e05617388d49a4778de53839ed6badf44"}}}}
{"actor":{"display_login":"akalin-keybase"}}