GitHub PullRequestReviewCommentEvent at 2014-09-02 21:24:52
Repository: node
Actor: hayes
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/joyent/node/pulls/comments/17017035","id":17017035,"diff_hunk":"@@ -34,18 +35,204 @@ Stream.PassThrough = require('_stream_passthrough');\n // Backwards-compat with node 0.4.x\n Stream.Stream = Stream;\n \n-\n-\n // old-style streams.  Note that the pipe method (the only relevant\n // part of this class) is overridden in the Readable class.\n \n function Stream() {\n   EE.call(this);\n+\n+  // note that we can't necessarily\n+  // count on these attributes to be present\n+  // on all streams -- some folk might subclass\n+  // incorrectly.\n+  this._nextStreams = [];\n+  this._prevStreams = [];\n+  this._errorHandlers = [];\n+}\n+\n+Stream.prototype.next = function() {\n+  return this._nextStreams.slice();\n+};\n+\n+Stream.prototype.prev = function() {\n+  return this._prevStreams.slice();\n+};\n+\n+Stream.prototype.nextAll = function() {\n+  return iterateStream(this, '_nextStreams');\n+};\n+\n+Stream.prototype.prevAll = function() {\n+  return iterateStream(this, '_prevStreams');\n+};\n+\n+Stream.prototype.removePipelineErrorHandler = function(fn) {\n+  if (!this._errorHandlers) return;\n+\n+  for (var i = 0, len = this._errorHandlers.length; i < len; ++i) {\n+    if (this._errorHandlers[i].handler === fn) {\n+      this._errorHandlers[i].uninstall();\n+\n+      return;\n+    }\n+  }\n+};\n+\n+Stream.prototype.addPipelineErrorHandler = function(fn) {\n+  if (!this._errorHandlers) {\n+    this._errorHandlers = [];\n+  }\n+\n+  var isHandling = true;\n+  var installedOnStreams = [];\n+\n+  // Using a WeakSet + array pair to speed up\n+  // membership lookups. Could be accomplished\n+  // with just the array.\n+  var installedMembership = new WeakSet;\n+  var source = this;\n+\n+  install(this);\n+  this.prevAll().forEach(install);\n+\n+  var installedIdx = this._errorHandlers.push({\n+    handler: fn,\n+    uninstall: uninstallAll\n+  }) - 1;\n+\n+  return this;\n+\n+  function install(stream, idx, all) {\n+    if (installedMembership.has(stream)) {\n+      return;\n+    }\n+\n+    installedOnStreams.push(stream);\n+    installedMembership.add(stream);\n+    stream.on('_preError', onPreError);\n+    stream.on('unpipe', onunpipe);\n+    stream.on('pipe', onpipe);\n+\n+    if (EE.listenerCount(stream, 'error') === 0) {\n+      stream.on('error', _defaultPipelineErrorHandler);\n+    }\n+  }\n+\n+  function onpipe(newSrc) {\n+    this.removeListener('error', _defaultPipelineErrorHandler);\n+    install(newSrc);\n+    newSrc.prevAll().forEach(install);\n+  }\n+\n+  function onunpipe() {\n+    uninstallAll();\n+    source.addPipelineErrorHandler(fn);\n+  }\n+\n+  function onPreError(err, handled) {\n+    handled.handled = maybePropagate(this, err);\n+  }\n+\n+  function uninstall(stream) {\n+    stream.removeListener('_preError', onPreError);\n+    stream.removeListener('error', _defaultPipelineErrorHandler);\n+    stream.removeListener('pipe', onpipe);\n+    stream.removeListener('unpipe', onunpipe);\n+\n+    if (!stream._errorHandlers) return;\n+    if (stream !== source) return;\n+    if (installedIdx === null) return;\n+\n+    stream._errorHandlers.splice(installedIdx, 1);\n+    installedIdx = null;","path":"lib/stream.js","position":123,"original_position":123,"commit_id":"84ba49e2d80d59b5aa3b386271722e4d5d9a367a","original_commit_id":"84ba49e2d80d59b5aa3b386271722e4d5d9a367a","user":{"login":"hayes","id":816527,"avatar_url":"https://avatars.githubusercontent.com/u/816527?v=2","gravatar_id":"b433df873f69cafc0688569cbf42e405","url":"https://api.github.com/users/hayes","html_url":"https://github.com/hayes","followers_url":"https://api.github.com/users/hayes/followers","following_url":"https://api.github.com/users/hayes/following{/other_user}","gists_url":"https://api.github.com/users/hayes/gists{/gist_id}","starred_url":"https://api.github.com/users/hayes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hayes/subscriptions","organizations_url":"https://api.github.com/users/hayes/orgs","repos_url":"https://api.github.com/users/hayes/repos","events_url":"https://api.github.com/users/hayes/events{/privacy}","received_events_url":"https://api.github.com/users/hayes/received_events","type":"User","site_admin":false},"body":"ah cool, wasn't really sure how implementations for browserify and the polyfill for older versions of node would work.  ","created_at":"2014-09-02T21:24:52Z","updated_at":"2014-09-02T21:24:52Z","html_url":"https://github.com/joyent/node/pull/8306#discussion_r17017035","pull_request_url":"https://api.github.com/repos/joyent/node/pulls/8306","_links":{"self":{"href":"https://api.github.com/repos/joyent/node/pulls/comments/17017035"},"html":{"href":"https://github.com/joyent/node/pull/8306#discussion_r17017035"},"pull_request":{"href":"https://api.github.com/repos/joyent/node/pulls/8306"}}},"pull_request":{"url":"https://api.github.com/repos/joyent/node/pulls/8306","id":20627739,"html_url":"https://github.com/joyent/node/pull/8306","diff_url":"https://github.com/joyent/node/pull/8306.diff","patch_url":"https://github.com/joyent/node/pull/8306.patch","issue_url":"https://api.github.com/repos/joyent/node/issues/8306","number":8306,"state":"open","locked":false,"title":"Pipeline Errors - Forwarding errors down stream pipelines","user":{"login":"chrisdickinson","id":37303,"avatar_url":"https://avatars.githubusercontent.com/u/37303?v=2","gravatar_id":"f70956bdb907c2f8b39ff624ea925ccd","url":"https://api.github.com/users/chrisdickinson","html_url":"https://github.com/chrisdickinson","followers_url":"https://api.github.com/users/chrisdickinson/followers","following_url":"https://api.github.com/users/chrisdickinson/following{/other_user}","gists_url":"https://api.github.com/users/chrisdickinson/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisdickinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisdickinson/subscriptions","organizations_url":"https://api.github.com/users/chrisdickinson/orgs","repos_url":"https://api.github.com/users/chrisdickinson/repos","events_url":"https://api.github.com/users/chrisdickinson/events{/privacy}","received_events_url":"https://api.github.com/users/chrisdickinson/received_events","type":"User","site_admin":false},"body":"**Note:** This is by way of proposal -- I'm opening this now in its current form to get more comments. In  particular, I have the following questions:\r\n\r\nWould it be better to distinguish the error re-throwing behavior from the unpiping behavior? This would entail\r\nsplitting up `handleError` into `handleError` (to prevent re-throwing the error) and `preventDefault` (to prevent unpiping) -- or alternatively, should the presence of a pipelineError handler be enough to prevent the re-throwing of errors?\r\n\r\ncc @phated @contra @tjfontaine @hayes @dominictarr @substack\r\n\r\n# pipeline errors\r\n\r\nWriting correct streaming programs is difficult. A single stream missing an\r\nerror handler in a pipeline has the ability to crash your entire program, often\r\nwith a cryptic error message. Even when an error listener is added, the stream\r\nsystem has already unpiped by the time client code is called. This makes it\r\ndifficult to generically gather pipeline information on error events.\r\nFinally, error events are not necessarily fatal to the pipeline, but the user\r\nhas no way to communicate this intent back to the stream subsystem -- any error\r\nemitted *will* destroy whatever pipeline it is emitted in, at present.\r\nTo reiterate: error handling in pipelines requires users to remember to add\r\nerror listeners to every constituent stream, does not allow users to prevent\r\nundesired default behavior, and in the event of an error, does not give users\r\nenough information to debug the situation.\r\n\r\nThe goals and constraints of this proposal are as follows:\r\n\r\n* Make correct error handling easier to implement for end users.\r\n* The solution must be polyfillable for older versions of node.\r\n* The solution must work in browser (for browserify).\r\n* Work with extant stream solutions (through, through2).\r\n* Provide public methods into internal state for being able to traverse\r\nup and down pipelines.\r\n* Implement the solution in terms of public methods, inasfar as possible.\r\n\r\nTo that end, this proposal adds six methods (in three pairs) to the Stream class.\r\n\r\n* `stream.next()` and `stream.prev()`: Get an array of the set \r\nof *immediate* destination or source streams.\r\n* `stream.nextAll()` and `stream.prevAll()`: Get an array of the \r\nset of all destination or source streams, with no duplicates or cycles.\r\n* `stream.addPipelineErrorHandler(fn)` and `stream.removePipelineErrorHandler(fn)`: \r\nAdd a pipeline error handler to a stream.\r\n\r\n## User-facing API\r\n\r\n```javascript\r\nvar A = fs.createReadStream('input');\r\nvar B = new stream.PassThrough();\r\nvar C = fs.createWriteStream('output');\r\n\r\nA.name = 'A';\r\nB.name = 'B';\r\nC.name = 'C';\r\n\r\nC.addPipelineErrorHandler(function(ev) {\r\n  console.log(ev.error.message, ev.stream.name);\r\n  ev.handleError();\r\n});\r\n\r\nA.pipe(B).pipe(C);\r\nA.emit('error', new Error('hello!')); // logs \"hello!\".\r\n```\r\n\r\nFor example, in the above program, someone has maliciously injected an error\r\nevent into our stream pipeline. The error originates on `A`, but is handled at\r\n`C`. The error handler indicates that the user wishes to manually handle this error\r\nevent and prevent the unpiping behavior with `ev.handleError()`. The original error\r\nand the originating stream are available at `ev.error` and `ev.stream`, respectively.\r\n\r\nThe error event may only be marked \"handled\" on the same event loop turn as it was\r\nemitted. Multiple pipeline error handlers may intercept an error -- it propagates downstream,\r\nhitting handlers \"A\" then \"B\" then \"C\". Once an error event is \"handled\", it stops propagating\r\nentirely. For complex pipelines -- where there may be cycles, or multiple branches, it propagates\r\ndown the \"oldest\" pipelines first, stopping when it hits duplicates. A few examples:\r\n\r\n```\r\nA - B - A     error at A, propagates to A, then B.\r\n*\r\n\r\nA - B - A     error at B, propagates to B, then A.\r\n    *\r\n\r\nA - B - C     error at A, propagates to A, B, then C.\r\n*\r\n\r\nA - B - C     error at B, propagates to B, then C.\r\n    *\r\n\r\n       C - D  error at A, propagates to A, B, C, D, X, then Y.\r\n      /       NB: handlers on *either* branch have the opportunity\r\nA - B         to \"handle\" the error and thus stop propagation.\r\n*     \\\r\n       X - Y\r\n```\r\n\r\nA user may remove a handler by calling `stream.removePipelineErrorHandler(fn)` with the handler\r\nfunction they originally provided.\r\n\r\n## Implementation\r\n\r\nThe sequence of events during an error is as follows:\r\n\r\n1. A.emit(\"error\")\r\n2. stream .pipe \"error\" handler (or `_defaultErrorHandler`).\r\n3. Internal \"\\_preError\" event.\r\n4. pipelineErrorHandlers for A, and all downstream streams (gathered with `nextAll`) until error is handled or streams exhausted.\r\n5. if the error is handled, skip to step 8.\r\n6. unpipe().\r\n7. re-emit error if no other error handlers exist.\r\n8. The rest of the \"error\" handlers on A.\r\n\r\nIn order to implement this sequence of events, the following modifications and\r\nadditions were made:\r\n\r\n### Added `_nextStreams` and `_prevStreams` attributes to base Stream class\r\n\r\nThe stream constructor grew two new arrays -- `_nextStreams` and\r\n`_prevStreams`. These are arrays tracking the next and previous streams, and\r\nare managed internally by the `.pipe` method (both `Readable#pipe` and\r\n`Stream#pipe` may affect these arrays. Since \"correct\" subclassing isn't\r\nguaranteed (see crypto's `LazyTransform`), the attributes are [lazily](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/_stream_readable.js#L466-L467) [added](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L230-L231) \r\nby `*#pipe` if they are not present. These attributes are there to support \r\nthe `{next,prev}{All,}()` methods. **`_nextStreams`** is a slight duplication\r\nof information in the `Readable` case, but it is only ever represented by an\r\nArray, while `_readableState.pipes` may be null, a single Stream, or an array\r\nof Streams.\r\n\r\n### Added `{next,prev}{All,}()` methods\r\n\r\nTo give users better tools to debug errors within streams, the `next()`,\r\n`prev()`, `nextAll()`, and `prevAll()` methods were added to publicly expose\r\nwhat was previously private or implicit state. `nextAll` and `prevAll` are implemented\r\nin terms of `iterateStream(stream, attributeName)` ([here](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L61-L67) and [here](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L202-L225)), which does a (non-recursive) depth\r\nfirst traversal of the `_nextStreams` (or `_prevStreams`) property. One potentially\r\ncontroversial bit is the [use of a `WeakSet`](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L92) to ensure no duplicates are added to the\r\noutput -- this could be replaced with an array of \"seen\" streams in environments that\r\ndon't support `WeakSet`.\r\n\r\n### Modified `onerror` in `{Readable,Stream}#pipe`\r\n\r\nThis is the primary change:\r\n\r\n```javascript\r\nfunction onerror(er) {\r\n  var handled = {handled: false};\r\n  this.emit('_preError', er, handled);\r\n\r\n  if (handled.handled) {\r\n    return;\r\n  }\r\n\r\n  // ...\r\n```\r\n\r\nThis has been added for [old streams](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L279-L284) as\r\nwell as [new streams](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/_stream_readable.js#L563-L568). The goal\r\nwas to keep all of the pipeline error event handling **out** of the pipe functions. Earlier attempts, where the pipelineError event was\r\nemitted as a \"normal\" EE-style event, and the handling logic was embedded in `.pipe` became complicated *very* quickly. There's bookkeeping\r\nthat needs to be done with regards to default error handlers, deciding which error handler needs to propagate the PipelineErrorEvent, etc.,\r\nand I wanted to keep that as separate from `.pipe` as possible. In order to do this, I added an internal event -- `\"_preError\"` -- that emits\r\nthe error and an object that can be modified by listeners of `_preError` to indicate whether the error is handled or not.\r\n\r\nOne benefit of this approach is that the feature can be added piecemeal -- adding the navigational components can be one piece, adding `_preError`\r\ncan be another, and we can iterate on what the public API for `_preError` should be (if the current approach is deemed unsatisfactory).\r\n\r\n### Added `_errorHandlers` attribute to base Stream class\r\n\r\nThis is pretty straightforward. Like `_{next,prev}Streams`, it's an attribute that's\r\nlazily added if not present; otherwise it contains a list of `{handler, uninstall}` \r\nobjects. `{add,remove}PipelineErrorHandler` manipulates this attribute.\r\n\r\n### Added `addPipelineErrorEvent`\r\n\r\nThis is the [real meat](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L81-L154) of the change. It installs listeners onto the current\r\nstream as well as all streams returned by `prevAll()`. Installation adds\r\nlisteners for `unpipe`, `pipe`, `_preError`, and, if there are no `error`\r\nhandlers present, `error` as well.\r\n\r\nThe `error` event handler is called `_defaultPipelineErrorHandler` ([here](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L156-L171)). Because streams2+\r\ndo not add an error event handler to Readable streams on `.pipe`, this ensures consistent\r\npipeline error behavior -- namely, that errors emitted on Readables are forwarded down the\r\npipeline.\r\n\r\nBecause upstream may change over time (after the initial\r\n`addPipelineErrorHandler` call), a listener is added for `pipe` and\r\n`unpipe`. [Once piped to](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L121-L125), a pipeline-error-handling stream will remove the\r\n`_defaultPipelineErrorHandler`, and install the 3 to 4 listeners on the new\r\nsource and all of its ancestors. [If unpiped](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L127-L130), to keep things simple, the handler\r\nsimply uninstalls and reinstalls itself, re-propagating the error handlers out\r\nappropriately.\r\n\r\nUninstallation involves [removing all installed listeners](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L137-L140), and [splicing out](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L142-L147) the\r\nerrorHandler from the list of error handlers if the stream is the source\r\nstream. Since the streams installed are tracked by the `installedOnStreams`\r\narray (deduplicated with the aid of a `WeakSet`), uninstallation is as simple as\r\ncalling `uninstall` on [each of the streams](https://github.com/chrisdickinson/node/blob/feature/adderrorhandler/lib/stream.js#L150-L153).","created_at":"2014-09-02T17:32:27Z","updated_at":"2014-09-02T21:24:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a642167e13ddc6776768de58b892ef9c8917fc6e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/joyent/node/pulls/8306/commits","review_comments_url":"https://api.github.com/repos/joyent/node/pulls/8306/comments","review_comment_url":"https://api.github.com/repos/joyent/node/pulls/comments/{number}","comments_url":"https://api.github.com/repos/joyent/node/issues/8306/comments","statuses_url":"https://api.github.com/repos/joyent/node/statuses/84ba49e2d80d59b5aa3b386271722e4d5d9a367a","head":{"label":"chrisdickinson:feature/adderrorhandler","ref":"feature/adderrorhandler","sha":"84ba49e2d80d59b5aa3b386271722e4d5d9a367a","user":{"login":"chrisdickinson","id":37303,"avatar_url":"https://avatars.githubusercontent.com/u/37303?v=2","gravatar_id":"f70956bdb907c2f8b39ff624ea925ccd","url":"https://api.github.com/users/chrisdickinson","html_url":"https://github.com/chrisdickinson","followers_url":"https://api.github.com/users/chrisdickinson/followers","following_url":"https://api.github.com/users/chrisdickinson/following{/other_user}","gists_url":"https://api.github.com/users/chrisdickinson/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisdickinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisdickinson/subscriptions","organizations_url":"https://api.github.com/users/chrisdickinson/orgs","repos_url":"https://api.github.com/users/chrisdickinson/repos","events_url":"https://api.github.com/users/chrisdickinson/events{/privacy}","received_events_url":"https://api.github.com/users/chrisdickinson/received_events","type":"User","site_admin":false},"repo":{"id":20460219,"name":"node","full_name":"chrisdickinson/node","owner":{"login":"chrisdickinson","id":37303,"avatar_url":"https://avatars.githubusercontent.com/u/37303?v=2","gravatar_id":"f70956bdb907c2f8b39ff624ea925ccd","url":"https://api.github.com/users/chrisdickinson","html_url":"https://github.com/chrisdickinson","followers_url":"https://api.github.com/users/chrisdickinson/followers","following_url":"https://api.github.com/users/chrisdickinson/following{/other_user}","gists_url":"https://api.github.com/users/chrisdickinson/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisdickinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisdickinson/subscriptions","organizations_url":"https://api.github.com/users/chrisdickinson/orgs","repos_url":"https://api.github.com/users/chrisdickinson/repos","events_url":"https://api.github.com/users/chrisdickinson/events{/privacy}","received_events_url":"https://api.github.com/users/chrisdickinson/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/chrisdickinson/node","description":"evented I/O for v8 javascript","fork":true,"url":"https://api.github.com/repos/chrisdickinson/node","forks_url":"https://api.github.com/repos/chrisdickinson/node/forks","keys_url":"https://api.github.com/repos/chrisdickinson/node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/chrisdickinson/node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/chrisdickinson/node/teams","hooks_url":"https://api.github.com/repos/chrisdickinson/node/hooks","issue_events_url":"https://api.github.com/repos/chrisdickinson/node/issues/events{/number}","events_url":"https://api.github.com/repos/chrisdickinson/node/events","assignees_url":"https://api.github.com/repos/chrisdickinson/node/assignees{/user}","branches_url":"https://api.github.com/repos/chrisdickinson/node/branches{/branch}","tags_url":"https://api.github.com/repos/chrisdickinson/node/tags","blobs_url":"https://api.github.com/repos/chrisdickinson/node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/chrisdickinson/node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/chrisdickinson/node/git/refs{/sha}","trees_url":"https://api.github.com/repos/chrisdickinson/node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/chrisdickinson/node/statuses/{sha}","languages_url":"https://api.github.com/repos/chrisdickinson/node/languages","stargazers_url":"https://api.github.com/repos/chrisdickinson/node/stargazers","contributors_url":"https://api.github.com/repos/chrisdickinson/node/contributors","subscribers_url":"https://api.github.com/repos/chrisdickinson/node/subscribers","subscription_url":"https://api.github.com/repos/chrisdickinson/node/subscription","commits_url":"https://api.github.com/repos/chrisdickinson/node/commits{/sha}","git_commits_url":"https://api.github.com/repos/chrisdickinson/node/git/commits{/sha}","comments_url":"https://api.github.com/repos/chrisdickinson/node/comments{/number}","issue_comment_url":"https://api.github.com/repos/chrisdickinson/node/issues/comments/{number}","contents_url":"https://api.github.com/repos/chrisdickinson/node/contents/{+path}","compare_url":"https://api.github.com/repos/chrisdickinson/node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/chrisdickinson/node/merges","archive_url":"https://api.github.com/repos/chrisdickinson/node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/chrisdickinson/node/downloads","issues_url":"https://api.github.com/repos/chrisdickinson/node/issues{/number}","pulls_url":"https://api.github.com/repos/chrisdickinson/node/pulls{/number}","milestones_url":"https://api.github.com/repos/chrisdickinson/node/milestones{/number}","notifications_url":"https://api.github.com/repos/chrisdickinson/node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/chrisdickinson/node/labels{/name}","releases_url":"https://api.github.com/repos/chrisdickinson/node/releases{/id}","created_at":"2014-06-03T21:22:27Z","updated_at":"2014-08-29T01:39:12Z","pushed_at":"2014-08-28T18:25:44Z","git_url":"git://github.com/chrisdickinson/node.git","ssh_url":"git@github.com:chrisdickinson/node.git","clone_url":"https://github.com/chrisdickinson/node.git","svn_url":"https://github.com/chrisdickinson/node","homepage":"http://nodejs.org/","size":104617,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":false,"has_downloads":false,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"joyent:master","ref":"master","sha":"912b5e05811fd24f09f9d65200a1561a4482f166","user":{"login":"joyent","id":10161,"avatar_url":"https://avatars.githubusercontent.com/u/10161?v=2","gravatar_id":"95c8b4070c2ba024f87a8fdca63e9d24","url":"https://api.github.com/users/joyent","html_url":"https://github.com/joyent","followers_url":"https://api.github.com/users/joyent/followers","following_url":"https://api.github.com/users/joyent/following{/other_user}","gists_url":"https://api.github.com/users/joyent/gists{/gist_id}","starred_url":"https://api.github.com/users/joyent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joyent/subscriptions","organizations_url":"https://api.github.com/users/joyent/orgs","repos_url":"https://api.github.com/users/joyent/repos","events_url":"https://api.github.com/users/joyent/events{/privacy}","received_events_url":"https://api.github.com/users/joyent/received_events","type":"Organization","site_admin":false},"repo":{"id":211666,"name":"node","full_name":"joyent/node","owner":{"login":"joyent","id":10161,"avatar_url":"https://avatars.githubusercontent.com/u/10161?v=2","gravatar_id":"95c8b4070c2ba024f87a8fdca63e9d24","url":"https://api.github.com/users/joyent","html_url":"https://github.com/joyent","followers_url":"https://api.github.com/users/joyent/followers","following_url":"https://api.github.com/users/joyent/following{/other_user}","gists_url":"https://api.github.com/users/joyent/gists{/gist_id}","starred_url":"https://api.github.com/users/joyent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joyent/subscriptions","organizations_url":"https://api.github.com/users/joyent/orgs","repos_url":"https://api.github.com/users/joyent/repos","events_url":"https://api.github.com/users/joyent/events{/privacy}","received_events_url":"https://api.github.com/users/joyent/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/joyent/node","description":"evented I/O for v8 javascript","fork":false,"url":"https://api.github.com/repos/joyent/node","forks_url":"https://api.github.com/repos/joyent/node/forks","keys_url":"https://api.github.com/repos/joyent/node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/joyent/node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/joyent/node/teams","hooks_url":"https://api.github.com/repos/joyent/node/hooks","issue_events_url":"https://api.github.com/repos/joyent/node/issues/events{/number}","events_url":"https://api.github.com/repos/joyent/node/events","assignees_url":"https://api.github.com/repos/joyent/node/assignees{/user}","branches_url":"https://api.github.com/repos/joyent/node/branches{/branch}","tags_url":"https://api.github.com/repos/joyent/node/tags","blobs_url":"https://api.github.com/repos/joyent/node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/joyent/node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/joyent/node/git/refs{/sha}","trees_url":"https://api.github.com/repos/joyent/node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/joyent/node/statuses/{sha}","languages_url":"https://api.github.com/repos/joyent/node/languages","stargazers_url":"https://api.github.com/repos/joyent/node/stargazers","contributors_url":"https://api.github.com/repos/joyent/node/contributors","subscribers_url":"https://api.github.com/repos/joyent/node/subscribers","subscription_url":"https://api.github.com/repos/joyent/node/subscription","commits_url":"https://api.github.com/repos/joyent/node/commits{/sha}","git_commits_url":"https://api.github.com/repos/joyent/node/git/commits{/sha}","comments_url":"https://api.github.com/repos/joyent/node/comments{/number}","issue_comment_url":"https://api.github.com/repos/joyent/node/issues/comments/{number}","contents_url":"https://api.github.com/repos/joyent/node/contents/{+path}","compare_url":"https://api.github.com/repos/joyent/node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/joyent/node/merges","archive_url":"https://api.github.com/repos/joyent/node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/joyent/node/downloads","issues_url":"https://api.github.com/repos/joyent/node/issues{/number}","pulls_url":"https://api.github.com/repos/joyent/node/pulls{/number}","milestones_url":"https://api.github.com/repos/joyent/node/milestones{/number}","notifications_url":"https://api.github.com/repos/joyent/node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/joyent/node/labels{/name}","releases_url":"https://api.github.com/repos/joyent/node/releases{/id}","created_at":"2009-05-27T16:29:46Z","updated_at":"2014-09-02T20:47:33Z","pushed_at":"2014-09-02T18:39:37Z","git_url":"git://github.com/joyent/node.git","ssh_url":"git@github.com:joyent/node.git","clone_url":"https://github.com/joyent/node.git","svn_url":"https://github.com/joyent/node","homepage":"http://nodejs.org/","size":492888,"stargazers_count":31680,"watchers_count":31680,"language":"JavaScript","has_issues":true,"has_downloads":false,"has_wiki":true,"forks_count":7070,"mirror_url":null,"open_issues_count":918,"forks":7070,"open_issues":918,"watchers":31680,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/joyent/node/pulls/8306"},"html":{"href":"https://github.com/joyent/node/pull/8306"},"issue":{"href":"https://api.github.com/repos/joyent/node/issues/8306"},"comments":{"href":"https://api.github.com/repos/joyent/node/issues/8306/comments"},"review_comments":{"href":"https://api.github.com/repos/joyent/node/pulls/8306/comments"},"review_comment":{"href":"https://api.github.com/repos/joyent/node/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/joyent/node/pulls/8306/commits"},"statuses":{"href":"https://api.github.com/repos/joyent/node/statuses/84ba49e2d80d59b5aa3b386271722e4d5d9a367a"}}}}