{"action":"created","comment":{"url":"https://api.github.com/repos/reverbrain/elliptics/pulls/comments/39632267","id":39632267,"diff_hunk":"@@ -393,6 +406,375 @@ static int dnet_iterator_callback_send(void *priv, void *data, uint64_t dsize)\n \treturn dnet_send_reply_threshold(send->st, send->cmd, data, dsize, 1);\n }\n \n+struct dnet_iterator_server_send_write_private {\n+\tatomic_t\t\t\trefcnt;\n+\tstruct dnet_server_send_ctl\t*send;\n+\tuint64_t\t\t\tdsize;\n+\tchar\t\t\t\tdata[0];\n+};\n+\n+static int dnet_iterator_server_send_complete(struct dnet_addr *addr, struct dnet_cmd *cmd, void *priv)\n+{\n+\tstruct dnet_iterator_server_send_write_private *wp = priv;\n+\tstruct dnet_server_send_ctl *send = wp->send;\n+\tstruct dnet_net_state *st = send->state;\n+\tint err = 0;\n+\n+\t(void) addr;\n+\n+\t/*\n+\t * We only care about write transaction completion, thus we only check number\n+\t * of transaction destructions. There will be many transactions (one for each remote\n+\t * group to write data into) and therefore this code chunk will be invoked multiple times.\n+\t *\n+\t * If we would like to accumulate write results, we would have to check transaction\n+\t * replies and associated data.\n+\t */\n+\tif (is_trans_destroyed(cmd)) {\n+\t\terr = -ENOENT;\n+\t\tif (cmd)\n+\t\t\terr = cmd->status;\n+\n+\t\t/*\n+\t\t * Write CAS error is not a real 'error' in that regard, that we do not remove\n+\t\t * local record, but also do not stop iterator.\n+\t\t *\n+\t\t * Setting @write_error forces iterator to stop.\n+\t\t */\n+\t\tif (err && !send->write_error && (err != -EBADFD))\n+\t\t\tsend->write_error = err;\n+\n+\t\tif (atomic_dec_and_test(&wp->refcnt)) {\n+\t\t\tif (send->iflags & DNET_IFLAGS_MOVE) {\n+\t\t\t\tif (!err) {\n+\t\t\t\t\tstruct dnet_io_req *r;\n+\t\t\t\t\tstruct dnet_cmd *lc;\n+\t\t\t\t\tstruct dnet_io_attr *io;\n+\t\t\t\t\tsize_t cmd_size = sizeof(struct dnet_io_req) +\n+\t\t\t\t\t\t\tsizeof(struct dnet_cmd) +\n+\t\t\t\t\t\t\tsizeof(struct dnet_io_attr);\n+\n+\t\t\t\t\tr = malloc(cmd_size);\n+\t\t\t\t\tif (!r) {\n+\t\t\t\t\t\terr = -ENOMEM;\n+\t\t\t\t\t\t// we could update wp->data here, which is dnet_iterator_response\n+\t\t\t\t\t\t// but we do not really care about local errors, for example\n+\t\t\t\t\t\t// remove error is not handled too\n+\t\t\t\t\t\tgoto err_out_send;\n+\t\t\t\t\t}\n+\n+\t\t\t\t\tmemset(r, 0, cmd_size);\n+\n+\t\t\t\t\tr->header = r + 1;\n+\t\t\t\t\tr->hsize = sizeof(struct dnet_cmd);\n+\t\t\t\t\tr->data = r->header + sizeof(struct dnet_cmd);\n+\t\t\t\t\tr->dsize = sizeof(struct dnet_io_attr);\n+\t\t\t\t\tr->st = dnet_state_get(st);\n+\n+\t\t\t\t\tlc = r->header;\n+\t\t\t\t\tdnet_setup_id(&lc->id, send->cmd.id.group_id, cmd->id.id);\n+\t\t\t\t\tlc->cmd = DNET_CMD_DEL;\n+\t\t\t\t\tlc->backend_id = -1;\n+\t\t\t\t\tlc->trace_id = cmd->trace_id;\n+\t\t\t\t\tlc->flags = DNET_FLAGS_NOLOCK;\n+\t\t\t\t\tlc->size = sizeof(struct dnet_io_attr);\n+\n+\t\t\t\t\tio = r->data;\n+\t\t\t\t\tio->flags = DNET_IO_FLAGS_SKIP_SENDING;\n+\t\t\t\t\tmemcpy(io->id, lc->id.id, DNET_ID_SIZE);\n+\t\t\t\t\tmemcpy(io->parent, lc->id.id, DNET_ID_SIZE);\n+\t\t\t\t\tdnet_convert_io_attr(io);\n+\n+\t\t\t\t\tdnet_schedule_io(st->n, r);\n+\t\t\t\t}\n+\t\t\t}\n+\n+err_out_send:\n+\t\t\terr = dnet_send_reply(send->state, &send->cmd, wp->data, wp->dsize, 1);\n+\t\t\tif (err && !send->write_error)\n+\t\t\t\tsend->write_error = err;\n+\n+\t\t\tstruct dnet_iterator_response *re = (struct dnet_iterator_response *)wp->data;\n+\t\t\tdnet_convert_iterator_response(re);\n+\t\t\tdnet_log(st->n, DNET_LOG_NOTICE, \"%s: %s: sending response to client: %s, \"\n+\t\t\t\t\t\"user_flags: %llx, ts: %lld.%09lld, \"\n+\t\t\t\t\t\"status: %d, size: %lld, iterated_keys: %lld/%lld, write_error: %d\",\n+\t\t\t\t\t__func__,\n+\t\t\t\t\tdnet_dump_id(&send->cmd.id), dnet_dump_id_str(re->key.id),\n+\t\t\t\t\t(unsigned long long)re->user_flags,\n+\t\t\t\t\t(unsigned long long)re->timestamp.tsec, (unsigned long long)re->timestamp.tnsec,\n+\t\t\t\t\tre->status, (unsigned long long)re->size,\n+\t\t\t\t\t(unsigned long long)re->iterated_keys, (unsigned long long)re->total_keys,\n+\t\t\t\t\tsend->write_error);\n+\n+\t\t\tatomic_dec(&send->writes_pending);\n+\t\t\tpthread_cond_broadcast(&send->write_wait);\n+\n+\t\t\tdnet_server_send_put(send);\n+\t\t\tfree(wp);\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn err;\n+}\n+\n+struct dnet_server_send_ctl *dnet_server_send_alloc(void *state, struct dnet_cmd *cmd, uint64_t iflags,\n+\t\tint *groups, int group_num)\n+{\n+\tint err;\n+\tstruct dnet_net_state *st = state;\n+\tstruct dnet_server_send_ctl *ctl;\n+\n+\tdnet_state_get(st);\n+\n+\tctl = malloc(sizeof(struct dnet_server_send_ctl) + sizeof(int) * group_num);\n+\tif (!ctl) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_out_exit;\n+\t}\n+\n+\tmemset(ctl, 0, sizeof(struct dnet_server_send_ctl));\n+\n+\tctl->state = state;\n+\tctl->cmd = *cmd;\n+\tctl->iflags = iflags;\n+\tctl->groups = (int *)(ctl + 1);\n+\tmemcpy(ctl->groups, groups, sizeof(int) * group_num);\n+\tctl->group_num = group_num;\n+\tatomic_set(&ctl->writes_pending, 0);\n+\tatomic_set(&ctl->refcnt, 1);\n+\n+\terr = pthread_cond_init(&ctl->write_wait, NULL);\n+\tif (err) {\n+\t\terr = -err;\n+\t\tdnet_log(st->n, DNET_LOG_ERROR, \"Failed to initialize server send condition variable: %d\", err);\n+\t\tgoto err_out_free;\n+\t}\n+\n+\terr = pthread_mutex_init(&ctl->write_lock, NULL);\n+\tif (err) {\n+\t\terr = -err;\n+\t\tdnet_log(st->n, DNET_LOG_ERROR, \"Failed to initialize server send write lock: %d\", err);\n+\t\tgoto err_out_cond_destroy;\n+\t}\n+\n+\treturn ctl;\n+\n+err_out_cond_destroy:\n+\tpthread_cond_destroy(&ctl->write_wait);\n+err_out_free:\n+\tfree(ctl);\n+err_out_exit:\n+\tdnet_state_put(st);\n+\treturn NULL;\n+}\n+\n+static int dnet_server_send_cleanup(struct dnet_server_send_ctl *ctl)\n+{\n+\tint err = ctl->write_error;\n+\n+\t/*\n+\t * This bit will not be set for iterator request.\n+\t * It can be set for async operations,\n+\t * for example DNET_CMD_SEND handler sets up a bunch of async\n+\t * WRITE requests and returns. When all writes will have been completed,\n+\t * this cleanup function is invoked, but since they were async,\n+\t * client hasn't received ACK and there is no blocking\n+\t * thread waiting for all requests to complete (like in iterator command)\n+\t */\n+\tif (ctl->cmd.flags & DNET_FLAGS_NEED_ACK) {\n+\t\tdnet_send_ack(ctl->state, &ctl->cmd, err, 0);\n+\t}\n+\n+\tpthread_cond_destroy(&ctl->write_wait);\n+\tpthread_mutex_destroy(&ctl->write_lock);\n+\n+\tdnet_state_put(ctl->state);\n+\tfree(ctl);\n+\n+\treturn err;\n+}\n+\n+struct dnet_server_send_ctl *dnet_server_send_get(struct dnet_server_send_ctl *ctl)\n+{\n+\tatomic_inc(&ctl->refcnt);\n+\treturn ctl;\n+}\n+int dnet_server_send_put(struct dnet_server_send_ctl *ctl)\n+{\n+\tif (atomic_dec_and_test(&ctl->refcnt))\n+\t\treturn dnet_server_send_cleanup(ctl);\n+\n+\treturn 0;\n+}\n+\n+static int dnet_server_send_sync(struct dnet_server_send_ctl *ctl)\n+{\n+\twhile (atomic_read(&ctl->writes_pending) > 0) {\n+\t\tpthread_mutex_lock(&ctl->write_lock);\n+\t\tpthread_cond_wait(&ctl->write_wait, &ctl->write_lock);\n+\t\tpthread_mutex_unlock(&ctl->write_lock);\n+\t}\n+\n+\treturn dnet_server_send_put(ctl);\n+}\n+\n+/*\n+ * Helper function which sends given data as WRITE command to remote groups\n+ */\n+int dnet_server_send_write(struct dnet_server_send_ctl *send,\n+\t\tstruct dnet_iterator_response *re, uint64_t dsize,\n+\t\tint fd, uint64_t data_offset)\n+{\n+\tstruct dnet_net_state *client_state = send->state;\n+\tstruct dnet_node *n = client_state->n;\n+\tstruct dnet_io_control ctl;\n+\tstruct dnet_session *s;\n+\tstruct dnet_iterator_server_send_write_private *wp;\n+\tint err;\n+\n+\tdnet_server_send_get(send);\n+\n+\t/*\n+\t * If need_exit is set - skips sending reply and return -EINTR to\n+\t * interrupt execution of current iterator\n+\t */\n+\tif (client_state->__need_exit) {\n+\t\tdnet_log(n, DNET_LOG_ERROR, \"%s: Interrupting iterator because peer has been disconnected\",\n+\t\t\t\tdnet_dump_id(&send->cmd.id));\n+\t\terr = -EINTR;\n+\t\tgoto err_out_exit;\n+\t}\n+\n+\tmemset(&ctl, 0, sizeof(struct dnet_io_control));\n+\n+\tmemcpy(ctl.id.id, re->key.id, DNET_ID_SIZE);\n+\tctl.id.group_id = 0; // will be rewritten in dnet_trans_create_send_all()\n+\n+\tmemcpy(ctl.io.id, re->key.id, DNET_ID_SIZE);\n+\tctl.io.timestamp = re->timestamp;\n+\tctl.io.user_flags = re->user_flags;\n+\tctl.io.total_size = re->size;\n+\tctl.io.size = re->size;\n+\tctl.io.flags = DNET_IO_FLAGS_WRITE_NO_FILE_INFO;\n+\n+\t// overwrite doesn't care whether remote key differs from local\n+\t// when this flag is not set, we only overwrite the same data or if there is no remote copy at all\n+\tif (!(send->iflags & DNET_IFLAGS_OVERWRITE))\n+\t\tctl.io.flags |= DNET_IO_FLAGS_COMPARE_AND_SWAP;","path":"library/dnet.c","position":372,"original_position":372,"commit_id":"0c393a4883e352c5ce9357c8d4090ca9b9049cfe","original_commit_id":"0c393a4883e352c5ce9357c8d4090ca9b9049cfe","user":{"login":"shaitan","id":446423,"avatar_url":"https://avatars.githubusercontent.com/u/446423?v=3","gravatar_id":"","url":"https://api.github.com/users/shaitan","html_url":"https://github.com/shaitan","followers_url":"https://api.github.com/users/shaitan/followers","following_url":"https://api.github.com/users/shaitan/following{/other_user}","gists_url":"https://api.github.com/users/shaitan/gists{/gist_id}","starred_url":"https://api.github.com/users/shaitan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shaitan/subscriptions","organizations_url":"https://api.github.com/users/shaitan/orgs","repos_url":"https://api.github.com/users/shaitan/repos","events_url":"https://api.github.com/users/shaitan/events{/privacy}","received_events_url":"https://api.github.com/users/shaitan/received_events","type":"User","site_admin":false},"body":"You should change description for pull request and comments for this feature, because it says: \"writing data to remote node using compare-and-swap, i.e. only write data if it either doesn't exist or it is the same on remote servers\".","created_at":"2015-09-16T13:58:52Z","updated_at":"2015-09-16T13:58:52Z","html_url":"https://github.com/reverbrain/elliptics/pull/650#discussion_r39632267","pull_request_url":"https://api.github.com/repos/reverbrain/elliptics/pulls/650","_links":{"self":{"href":"https://api.github.com/repos/reverbrain/elliptics/pulls/comments/39632267"},"html":{"href":"https://github.com/reverbrain/elliptics/pull/650#discussion_r39632267"},"pull_request":{"href":"https://api.github.com/repos/reverbrain/elliptics/pulls/650"}}},"pull_request":{"url":"https://api.github.com/repos/reverbrain/elliptics/pulls/650","id":44994113,"html_url":"https://github.com/reverbrain/elliptics/pull/650","diff_url":"https://github.com/reverbrain/elliptics/pull/650.diff","patch_url":"https://github.com/reverbrain/elliptics/pull/650.patch","issue_url":"https://api.github.com/repos/reverbrain/elliptics/issues/650","number":650,"state":"open","locked":false,"title":"Server-send operations","user":{"login":"bioothod","id":1847575,"avatar_url":"https://avatars.githubusercontent.com/u/1847575?v=3","gravatar_id":"","url":"https://api.github.com/users/bioothod","html_url":"https://github.com/bioothod","followers_url":"https://api.github.com/users/bioothod/followers","following_url":"https://api.github.com/users/bioothod/following{/other_user}","gists_url":"https://api.github.com/users/bioothod/gists{/gist_id}","starred_url":"https://api.github.com/users/bioothod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bioothod/subscriptions","organizations_url":"https://api.github.com/users/bioothod/orgs","repos_url":"https://api.github.com/users/bioothod/repos","events_url":"https://api.github.com/users/bioothod/events{/privacy}","received_events_url":"https://api.github.com/users/bioothod/received_events","type":"User","site_admin":false},"body":"This pull request introduces 2 server-side operations which allow to send content from one server to multiple replicas. It is intended for various recovery tools which optimize by not copying data from local node to recovery temporary location, instead they may tell remote node to send data directly to required locations. It can also be used to move data from older eblob version to newer without server interruption.\r\n\r\n1. New iterator type which sends all keys being iterated to set of remote groups. It does it with the speed of network or disk (what it slower), in local tests iteration over 200Gb blobs sending data over the network to one remote node ended up with ~78MB/s sustained speed with pikes though, especially when remote node synced caches. Both sender and recipient had 30Gb of RAM. `rsync` shows ~32MB/s speed on these machines, but not because it is that slow, but because of `ssh` which maxed out CPU by packet encryption.\r\nIterator sends `dnet_iterator_response` structure for each write result for every key it has processed just like for usual iterator, neither API nor ABI is broken.\r\n\r\n2. Server-send command which accepts vector of keys. API searches for all remote nodes/backends which host given keys in the one specified group, splits keys into per-node/backend basis and tells remote backends to send appropriate keys to specified remote groups. The same iterator response is generated for every key which has been processed.\r\n\r\nThere are 3 operation modes:\r\n1. default - writing data to remote node using compare-and-swap, i.e. only write data if it either doesn't exist or it is the same on remote servers. Server send (iterator or per-key) running in this mode is especially useful for recovery - there is no way it can overwrite newer copy with the old data.\r\n2. overwrite - when special flag is set, overwrite data (clears compare-and-swap logic)\r\n3. move - if write has been successful, remove local key\r\n\r\nThere is example tool in example dir, which works the same way as `example/iterate.cpp`, but performs copy/move of the iterator results. There are 16 various tests which cover all operation modes. Tests are rather long - so far they are the longest, it takes about 170 seconds on my server.\r\n\r\nUnfortunately this pull request also contains fair number of bugfixes which should live in a separate commits. These include possible memory corruption, invalid pointer dereference and race conditions. I found them during extensive testing and failed to separate into own commits.","created_at":"2015-09-14T23:40:43Z","updated_at":"2015-09-16T13:58:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ed19db5b4a8fc25cdfdc5997e412dbaa76478ad5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/reverbrain/elliptics/pulls/650/commits","review_comments_url":"https://api.github.com/repos/reverbrain/elliptics/pulls/650/comments","review_comment_url":"https://api.github.com/repos/reverbrain/elliptics/pulls/comments{/number}","comments_url":"https://api.github.com/repos/reverbrain/elliptics/issues/650/comments","statuses_url":"https://api.github.com/repos/reverbrain/elliptics/statuses/0c393a4883e352c5ce9357c8d4090ca9b9049cfe","head":{"label":"bioothod:master","ref":"master","sha":"0c393a4883e352c5ce9357c8d4090ca9b9049cfe","user":{"login":"bioothod","id":1847575,"avatar_url":"https://avatars.githubusercontent.com/u/1847575?v=3","gravatar_id":"","url":"https://api.github.com/users/bioothod","html_url":"https://github.com/bioothod","followers_url":"https://api.github.com/users/bioothod/followers","following_url":"https://api.github.com/users/bioothod/following{/other_user}","gists_url":"https://api.github.com/users/bioothod/gists{/gist_id}","starred_url":"https://api.github.com/users/bioothod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bioothod/subscriptions","organizations_url":"https://api.github.com/users/bioothod/orgs","repos_url":"https://api.github.com/users/bioothod/repos","events_url":"https://api.github.com/users/bioothod/events{/privacy}","received_events_url":"https://api.github.com/users/bioothod/received_events","type":"User","site_admin":false},"repo":{"id":34638202,"name":"elliptics","full_name":"bioothod/elliptics","owner":{"login":"bioothod","id":1847575,"avatar_url":"https://avatars.githubusercontent.com/u/1847575?v=3","gravatar_id":"","url":"https://api.github.com/users/bioothod","html_url":"https://github.com/bioothod","followers_url":"https://api.github.com/users/bioothod/followers","following_url":"https://api.github.com/users/bioothod/following{/other_user}","gists_url":"https://api.github.com/users/bioothod/gists{/gist_id}","starred_url":"https://api.github.com/users/bioothod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bioothod/subscriptions","organizations_url":"https://api.github.com/users/bioothod/orgs","repos_url":"https://api.github.com/users/bioothod/repos","events_url":"https://api.github.com/users/bioothod/events{/privacy}","received_events_url":"https://api.github.com/users/bioothod/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bioothod/elliptics","description":"Distributed hashtable storage","fork":true,"url":"https://api.github.com/repos/bioothod/elliptics","forks_url":"https://api.github.com/repos/bioothod/elliptics/forks","keys_url":"https://api.github.com/repos/bioothod/elliptics/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bioothod/elliptics/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bioothod/elliptics/teams","hooks_url":"https://api.github.com/repos/bioothod/elliptics/hooks","issue_events_url":"https://api.github.com/repos/bioothod/elliptics/issues/events{/number}","events_url":"https://api.github.com/repos/bioothod/elliptics/events","assignees_url":"https://api.github.com/repos/bioothod/elliptics/assignees{/user}","branches_url":"https://api.github.com/repos/bioothod/elliptics/branches{/branch}","tags_url":"https://api.github.com/repos/bioothod/elliptics/tags","blobs_url":"https://api.github.com/repos/bioothod/elliptics/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bioothod/elliptics/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bioothod/elliptics/git/refs{/sha}","trees_url":"https://api.github.com/repos/bioothod/elliptics/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bioothod/elliptics/statuses/{sha}","languages_url":"https://api.github.com/repos/bioothod/elliptics/languages","stargazers_url":"https://api.github.com/repos/bioothod/elliptics/stargazers","contributors_url":"https://api.github.com/repos/bioothod/elliptics/contributors","subscribers_url":"https://api.github.com/repos/bioothod/elliptics/subscribers","subscription_url":"https://api.github.com/repos/bioothod/elliptics/subscription","commits_url":"https://api.github.com/repos/bioothod/elliptics/commits{/sha}","git_commits_url":"https://api.github.com/repos/bioothod/elliptics/git/commits{/sha}","comments_url":"https://api.github.com/repos/bioothod/elliptics/comments{/number}","issue_comment_url":"https://api.github.com/repos/bioothod/elliptics/issues/comments{/number}","contents_url":"https://api.github.com/repos/bioothod/elliptics/contents/{+path}","compare_url":"https://api.github.com/repos/bioothod/elliptics/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bioothod/elliptics/merges","archive_url":"https://api.github.com/repos/bioothod/elliptics/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bioothod/elliptics/downloads","issues_url":"https://api.github.com/repos/bioothod/elliptics/issues{/number}","pulls_url":"https://api.github.com/repos/bioothod/elliptics/pulls{/number}","milestones_url":"https://api.github.com/repos/bioothod/elliptics/milestones{/number}","notifications_url":"https://api.github.com/repos/bioothod/elliptics/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bioothod/elliptics/labels{/name}","releases_url":"https://api.github.com/repos/bioothod/elliptics/releases{/id}","created_at":"2015-04-27T00:42:00Z","updated_at":"2015-05-05T22:34:45Z","pushed_at":"2015-09-16T08:29:25Z","git_url":"git://github.com/bioothod/elliptics.git","ssh_url":"git@github.com:bioothod/elliptics.git","clone_url":"https://github.com/bioothod/elliptics.git","svn_url":"https://github.com/bioothod/elliptics","homepage":null,"size":20229,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"reverbrain:master","ref":"master","sha":"b3cb7134d5f149e81730f2cb91275329485247a7","user":{"login":"reverbrain","id":1616511,"avatar_url":"https://avatars.githubusercontent.com/u/1616511?v=3","gravatar_id":"","url":"https://api.github.com/users/reverbrain","html_url":"https://github.com/reverbrain","followers_url":"https://api.github.com/users/reverbrain/followers","following_url":"https://api.github.com/users/reverbrain/following{/other_user}","gists_url":"https://api.github.com/users/reverbrain/gists{/gist_id}","starred_url":"https://api.github.com/users/reverbrain/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/reverbrain/subscriptions","organizations_url":"https://api.github.com/users/reverbrain/orgs","repos_url":"https://api.github.com/users/reverbrain/repos","events_url":"https://api.github.com/users/reverbrain/events{/privacy}","received_events_url":"https://api.github.com/users/reverbrain/received_events","type":"Organization","site_admin":false},"repo":{"id":4712089,"name":"elliptics","full_name":"reverbrain/elliptics","owner":{"login":"reverbrain","id":1616511,"avatar_url":"https://avatars.githubusercontent.com/u/1616511?v=3","gravatar_id":"","url":"https://api.github.com/users/reverbrain","html_url":"https://github.com/reverbrain","followers_url":"https://api.github.com/users/reverbrain/followers","following_url":"https://api.github.com/users/reverbrain/following{/other_user}","gists_url":"https://api.github.com/users/reverbrain/gists{/gist_id}","starred_url":"https://api.github.com/users/reverbrain/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/reverbrain/subscriptions","organizations_url":"https://api.github.com/users/reverbrain/orgs","repos_url":"https://api.github.com/users/reverbrain/repos","events_url":"https://api.github.com/users/reverbrain/events{/privacy}","received_events_url":"https://api.github.com/users/reverbrain/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/reverbrain/elliptics","description":"Distributed storage for medium and large objects with streaming support","fork":false,"url":"https://api.github.com/repos/reverbrain/elliptics","forks_url":"https://api.github.com/repos/reverbrain/elliptics/forks","keys_url":"https://api.github.com/repos/reverbrain/elliptics/keys{/key_id}","collaborators_url":"https://api.github.com/repos/reverbrain/elliptics/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/reverbrain/elliptics/teams","hooks_url":"https://api.github.com/repos/reverbrain/elliptics/hooks","issue_events_url":"https://api.github.com/repos/reverbrain/elliptics/issues/events{/number}","events_url":"https://api.github.com/repos/reverbrain/elliptics/events","assignees_url":"https://api.github.com/repos/reverbrain/elliptics/assignees{/user}","branches_url":"https://api.github.com/repos/reverbrain/elliptics/branches{/branch}","tags_url":"https://api.github.com/repos/reverbrain/elliptics/tags","blobs_url":"https://api.github.com/repos/reverbrain/elliptics/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/reverbrain/elliptics/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/reverbrain/elliptics/git/refs{/sha}","trees_url":"https://api.github.com/repos/reverbrain/elliptics/git/trees{/sha}","statuses_url":"https://api.github.com/repos/reverbrain/elliptics/statuses/{sha}","languages_url":"https://api.github.com/repos/reverbrain/elliptics/languages","stargazers_url":"https://api.github.com/repos/reverbrain/elliptics/stargazers","contributors_url":"https://api.github.com/repos/reverbrain/elliptics/contributors","subscribers_url":"https://api.github.com/repos/reverbrain/elliptics/subscribers","subscription_url":"https://api.github.com/repos/reverbrain/elliptics/subscription","commits_url":"https://api.github.com/repos/reverbrain/elliptics/commits{/sha}","git_commits_url":"https://api.github.com/repos/reverbrain/elliptics/git/commits{/sha}","comments_url":"https://api.github.com/repos/reverbrain/elliptics/comments{/number}","issue_comment_url":"https://api.github.com/repos/reverbrain/elliptics/issues/comments{/number}","contents_url":"https://api.github.com/repos/reverbrain/elliptics/contents/{+path}","compare_url":"https://api.github.com/repos/reverbrain/elliptics/compare/{base}...{head}","merges_url":"https://api.github.com/repos/reverbrain/elliptics/merges","archive_url":"https://api.github.com/repos/reverbrain/elliptics/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/reverbrain/elliptics/downloads","issues_url":"https://api.github.com/repos/reverbrain/elliptics/issues{/number}","pulls_url":"https://api.github.com/repos/reverbrain/elliptics/pulls{/number}","milestones_url":"https://api.github.com/repos/reverbrain/elliptics/milestones{/number}","notifications_url":"https://api.github.com/repos/reverbrain/elliptics/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/reverbrain/elliptics/labels{/name}","releases_url":"https://api.github.com/repos/reverbrain/elliptics/releases{/id}","created_at":"2012-06-19T10:05:10Z","updated_at":"2015-08-19T20:53:50Z","pushed_at":"2015-09-16T08:29:25Z","git_url":"git://github.com/reverbrain/elliptics.git","ssh_url":"git@github.com:reverbrain/elliptics.git","clone_url":"https://github.com/reverbrain/elliptics.git","svn_url":"https://github.com/reverbrain/elliptics","homepage":"http://reverbrain.com/elliptics","size":28100,"stargazers_count":336,"watchers_count":336,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":62,"mirror_url":null,"open_issues_count":20,"forks":62,"open_issues":20,"watchers":336,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/reverbrain/elliptics/pulls/650"},"html":{"href":"https://github.com/reverbrain/elliptics/pull/650"},"issue":{"href":"https://api.github.com/repos/reverbrain/elliptics/issues/650"},"comments":{"href":"https://api.github.com/repos/reverbrain/elliptics/issues/650/comments"},"review_comments":{"href":"https://api.github.com/repos/reverbrain/elliptics/pulls/650/comments"},"review_comment":{"href":"https://api.github.com/repos/reverbrain/elliptics/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/reverbrain/elliptics/pulls/650/commits"},"statuses":{"href":"https://api.github.com/repos/reverbrain/elliptics/statuses/0c393a4883e352c5ce9357c8d4090ca9b9049cfe"}}}}