{"action":"created","comment":{"url":"https://api.github.com/repos/google/startup-os/pulls/comments/237444532","pull_request_review_id":179741790,"id":237444532,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNzQ0NDUzMg==","diff_hunk":"@@ -18,232 +18,215 @@\n \n import com.google.common.base.CharMatcher;\n import com.google.common.collect.ImmutableList;\n+import com.google.common.collect.ImmutableMap;\n import com.google.startupos.common.Protos.ChangeType;\n import com.google.startupos.name.fraser.neil.plaintext.DiffMatchPatch;\n import com.google.startupos.name.fraser.neil.plaintext.DiffMatchPatch.Operation;\n import com.google.startupos.common.Protos.TextDiff;\n-import com.google.startupos.common.Protos.TextChange;\n-import com.google.startupos.common.Protos.DiffPatchMatchChange;\n+import com.google.startupos.common.Protos.DiffLine;\n+import com.google.startupos.common.Protos.WordChange;\n+import com.google.startupos.common.repo.Protos.File;\n import javax.inject.Inject;\n import java.util.LinkedList;\n import java.util.List;\n import java.util.ArrayList;\n import java.util.Arrays;\n+import java.util.Map;\n import java.lang.Math;\n import java.lang.StringBuilder;\n+import java.util.regex.Matcher;\n+import java.util.regex.Pattern;\n \n /** Text differencer for finding the diff between 2 text documents. */\n public class TextDifferencer {\n+  static final Map<Character, ChangeType> CHANGE_TYPE_MAP =\n+      ImmutableMap.of(\n+          ' ', ChangeType.NO_CHANGE,\n+          '+', ChangeType.ADD,\n+          '-', ChangeType.DELETE);\n \n   @Inject\n   public TextDifferencer() {}\n \n-  private DiffPatchMatchChange convertToProto(DiffMatchPatch.Diff diff) {\n-    return DiffPatchMatchChange.newBuilder()\n-        .setText(diff.text)\n-        .setType(getChangeType(diff.operation))\n-        .build();\n-  }\n-\n-  private ImmutableList<DiffPatchMatchChange> getDiffPatchMatchChanges(\n-      String leftContents, String rightContents) {\n-    DiffMatchPatch diffMatchPatch = new DiffMatchPatch();\n-    LinkedList<DiffMatchPatch.Diff> diffs = diffMatchPatch.diff_main(leftContents, rightContents);\n-    diffMatchPatch.diff_cleanupSemantic(diffs);\n-    List<DiffPatchMatchChange> result = new ArrayList<DiffPatchMatchChange>();\n-    ChangeType previousChangeType = ChangeType.NO_CHANGE;\n-    for (DiffMatchPatch.Diff diff : diffs) {\n-      DiffPatchMatchChange change = convertToProto(diff);\n-      if (change.getType() == ChangeType.NO_CHANGE\n-          || previousChangeType == ChangeType.NO_CHANGE\n-          || previousChangeType == change.getType()) {\n-        result.add(change);\n-      } else {\n-        // Change last change to REPLACE:\n-        DiffPatchMatchChange.Builder replaceChange =\n-            makeReplaceChange(result.get(result.size() - 1)).toBuilder();\n-        if (change.getType() == ChangeType.ADD) {\n-          replaceChange.setReplacingText(replaceChange.getReplacingText() + change.getText());\n-        } else { // ChangeType.DELETE\n-          replaceChange.setText(replaceChange.getText() + change.getText());\n-        }\n-        result.set(result.size() - 1, replaceChange.build());\n-      }\n-      previousChangeType = change.getType();\n+  private void fillPlaceholderGap(\n+      TextDiff.Builder builder, int leftDiffLineNumber, int rightDiffLineNumber) {\n+    // Add left placeholders:\n+    while (rightDiffLineNumber - leftDiffLineNumber > 0) {\n+      builder.addLeftDiffLine(\n+          DiffLine.newBuilder()\n+              .setType(ChangeType.LINE_PLACEHOLDER)\n+              .setDiffLineNumber(leftDiffLineNumber)\n+              .build());\n+      leftDiffLineNumber++;\n+    }\n+    // Add right placeholders:\n+    while (leftDiffLineNumber - rightDiffLineNumber > 0) {\n+      builder.addRightDiffLine(\n+          DiffLine.newBuilder()\n+              .setType(ChangeType.LINE_PLACEHOLDER)\n+              .setDiffLineNumber(rightDiffLineNumber)\n+              .build());\n+      rightDiffLineNumber++;\n     }\n-    normalize(result);\n-    return ImmutableList.copyOf(result);\n   }\n \n-  // Normalize changes so they look better\n-  private void normalize(List<DiffPatchMatchChange> changes) {\n-    for (int i = 0; i < changes.size() - 1; i++) {\n-      DiffPatchMatchChange change = changes.get(i);\n-      if (change.getType() == ChangeType.REPLACE) {\n-        DiffPatchMatchChange nextChange = changes.get(i + 1);\n-        // If we're REPLACE and either text or replacing_text doesn't end in \\n, and the next\n-        // change is NO_CHANGE, and it starts with \\n, then move the \\n to the REPLACE change.\n-        // Example:\n-        // REPLACE: text=\"aaa\", replacing_text=\"bbb\\n\"\n-        // NO_CHANGE: text=\"\\nccc\\n\"\n-        // After normalization:\n-        // REPLACE: text=\"aaa\\n\", replacing_text=\"bbb\\n\\n\"\n-        // NO_CHANGE: text=\"ccc\\n\"\n-        if (nextChange.getType() == ChangeType.NO_CHANGE\n-            && nextChange.getText().startsWith(\"\\n\")\n-            && (!change.getText().endsWith(\"\\n\") || !change.getReplacingText().endsWith(\"\\n\"))) {\n-          changes.set(\n-              i,\n-              change\n-                  .toBuilder()\n-                  .setText(change.getText() + \"\\n\")\n-                  .setReplacingText(change.getReplacingText() + \"\\n\")\n-                  .build());\n-          changes.set(\n-              i + 1, nextChange.toBuilder().setText(nextChange.getText().substring(1)).build());\n-        }\n-      }\n-    }\n+  private int getLeftStartLine(String hunkHeader) {\n+    // Examples of diff hunk headers:\n+    // @@ -1 +1 @@\n+    // @@ -5,83 +5,83 @@\n+    // Remove 1 to be 0-based, so we subtract 1\n+    // It shouldn't be negative, although for some reason it is, so we truncate at 0.\n+    return Math.max(Integer.parseInt(hunkHeader.split(\" \")[1].substring(1).split(\",\")[0]) - 1, 0);\n   }\n \n-  private DiffPatchMatchChange makeReplaceChange(DiffPatchMatchChange change) {\n-    DiffPatchMatchChange.Builder replaceChange = change.toBuilder();\n-    if (change.getType() == ChangeType.ADD) {\n-      // Move text to replacing_text field\n-      replaceChange.setText(\"\");\n-      replaceChange.setReplacingText(change.getText());\n-    } else { // ChangeType.DELETE or ChangeType.REPLACE\n-      // Do nothing, text is already in the correct field\n-    }\n-    replaceChange.setType(ChangeType.REPLACE);\n-    return replaceChange.build();\n+  private int getRightStartLine(String hunkHeader) {\n+    return Math.max(Integer.parseInt(hunkHeader.split(\" \")[2].substring(1).split(\",\")[0]) - 1, 0);\n   }\n \n-  private ImmutableList<TextChange> getSingleSideChanges(\n-      ImmutableList<DiffPatchMatchChange> changes, ChangeType sideOperation) {\n-    if (changes.isEmpty()) {\n-      return ImmutableList.of();\n+  public TextDiff getTextDiff(String leftText, String rightText, String diffString) {\n+    // System.out.println(\"********* diffString *************\");\n+    // System.out.println(diffString);\n+    TextDiff.Builder result =\n+        TextDiff.newBuilder().setLeftFileContents(leftText).setRightFileContents(rightText);\n+    if (diffString.isEmpty()) {\n+      return result.build();\n     }\n-    ImmutableList.Builder<TextChange> result = ImmutableList.builder();\n-    StringBuilder sb = new StringBuilder();\n-    int lineNumber = 0;\n-    int lineIndex = 0;\n-    System.out.println(\"*************** CHANGES **************\");\n-    System.out.println(changes);\n-\n-    for (DiffPatchMatchChange change : changes) {\n-      ChangeType changeType = change.getType();\n-      String changeText = change.getText();\n-      if (sideOperation == ChangeType.ADD && change.getType() == ChangeType.REPLACE) {\n-        changeText = change.getReplacingText();\n-      }\n-      if (change.getType() == ChangeType.REPLACE) {\n-        // REPLACE acts as a \"joker\" - it's like both ADD and DELETE. We set it here to\n-        // sideOperation since it simplifies the code instead of checking for both every time.\n-        changeType = sideOperation;\n+    String[] diffLines = diffString.split(\"\\n\");\n+    int leftCodeLineNumber = getLeftStartLine(diffLines[0]);\n+    int leftDiffLineNumber = leftCodeLineNumber;\n+    int rightCodeLineNumber = getRightStartLine(diffLines[0]);\n+    int rightDiffLineNumber = rightCodeLineNumber;\n+    for (int diffIndex = 1; diffIndex < diffLines.length; diffIndex++) {\n+      if (diffLines[diffIndex].charAt(0) == '\\\\') {\n+        // This is not a real code line, probably \"\\ No newline at end of file\".\n+        continue;\n       }\n-      for (int i = 0; i < changeText.length(); i++) {\n-        char currentChar = changeText.charAt(i);\n-        if (currentChar == '\\n') {\n-          if (changeType == ChangeType.NO_CHANGE || changeType == sideOperation) {\n-            result.add(getTextChange(sb.toString(), lineNumber, lineIndex, changeType));\n-          } else {\n-            result.add(\n-                TextChange.newBuilder()\n-                    .setType(ChangeType.LINE_PLACEHOLDER)\n-                    .setLineNumber(lineNumber)\n-                    .build());\n-          }\n-          lineNumber++;\n-          sb = new StringBuilder();\n-          lineIndex = 0;\n-        } else { // char != '/n'\n-          if (changeType == ChangeType.NO_CHANGE || changeType == sideOperation) {\n-            lineIndex++;\n-          }\n-          sb.append(currentChar);\n-        }\n+      ChangeType type = CHANGE_TYPE_MAP.get(diffLines[diffIndex].charAt(0));\n+      if (type == null) {\n+        throw new IllegalStateException(\n+            \"Diff line \"\n+                + diffIndex\n+                + \" does not start with a diff character (+- ):\\n\"\n+                + diffLines[diffIndex]\n+                + \"\\nFor diffString:\\n\"\n+                + diffString);\n       }\n-      // Check for leftover from last line\n-      if (sb.length() > 0) {\n-        if (changeType == ChangeType.NO_CHANGE || changeType == sideOperation) {\n-          result.add(getTextChange(sb.toString(), lineNumber, lineIndex, changeType));\n-        }\n+      if (type == ChangeType.NO_CHANGE) {\n+        // On NO_CHANGE lines, we know that both diff line indices should be the same. We add\n+        // placeholder DiffLines to fill in the gaps:\n+        fillPlaceholderGap(result, leftDiffLineNumber, rightDiffLineNumber);\n+        // Now gap is filled, so set both line numbers to the maximum:\n+        leftDiffLineNumber = Math.max(leftDiffLineNumber, rightDiffLineNumber);\n+        rightDiffLineNumber = leftDiffLineNumber;\n+        // On to the next line:\n+        leftCodeLineNumber++;\n+        rightCodeLineNumber++;\n+        leftDiffLineNumber++;\n+        rightDiffLineNumber++;\n+        continue;\n       }\n-      // Add placeholders for REPLACE\n-      if (change.getType() == ChangeType.REPLACE) {\n-        int addPlaceholderCount =\n-            CharMatcher.is('\\n').countIn(change.getText())\n-                - CharMatcher.is('\\n').countIn(change.getReplacingText());\n-        if (sideOperation == ChangeType.DELETE) {\n-          addPlaceholderCount *= -1;\n-        }\n-        addPlaceholderCount = Math.max(addPlaceholderCount, 0);\n-        for (int i = 0; i < addPlaceholderCount; i++) {\n-          result.add(\n-              TextChange.newBuilder()\n-                  .setType(ChangeType.LINE_PLACEHOLDER)\n-                  .setLineNumber(lineNumber + i)\n-                  .build());\n-        }\n-        lineNumber += addPlaceholderCount;\n+      String text = diffLines[diffIndex].substring(1);\n+      int codeLineNumber = type == ChangeType.ADD ? rightCodeLineNumber : leftCodeLineNumber;\n+      int diffLineNumber = type == ChangeType.ADD ? rightDiffLineNumber : leftDiffLineNumber;\n+      DiffLine diffLine =\n+          DiffLine.newBuilder()\n+              .setText(text)\n+              .setType(type)\n+              .setCodeLineNumber(codeLineNumber)\n+              .setDiffLineNumber(diffLineNumber)\n+              .build();\n+      if (type == ChangeType.ADD) {\n+        result.addRightDiffLine(diffLine);\n+        rightCodeLineNumber++;\n+        rightDiffLineNumber++;\n+      } else {\n+        result.addLeftDiffLine(diffLine);\n+        leftCodeLineNumber++;\n+        leftDiffLineNumber++;\n       }\n-      sb = new StringBuilder();\n     }\n-    return normalize(result.build());\n+    // Fill any last section:\n+    fillPlaceholderGap(result, leftDiffLineNumber, rightDiffLineNumber);\n+    TextDiff textDiff = result.build();\n+    // TODO: Uncomment once addWordChanges() is complete.\n+    // addWordChanges(textDiff);\n+    System.out.println(\"*************** TEXT DIFF **************\");\n+    System.out.println(textDiff);\n+    return textDiff;\n+  }\n+\n+  private List<DiffMatchPatch.Diff> getDiffPatchMatchDiff(String leftText, String rightText) {\n+    DiffMatchPatch diffMatchPatch = new DiffMatchPatch();\n+    LinkedList<DiffMatchPatch.Diff> diffs = diffMatchPatch.diff_main(leftText, rightText);\n+    diffMatchPatch.diff_cleanupSemantic(diffs);\n+    return diffs;\n   }\n \n-  // Normalize changes, e.g combine subsequent changes of the same type\n-  private ImmutableList<TextChange> normalize(ImmutableList<TextChange> changes) {\n-    ArrayList<TextChange> result = new ArrayList(changes);\n-    for (int i = 0; i < result.size() - 1; i++) {\n-      TextChange change = result.get(i);\n-      TextChange nextChange = result.get(i + 1);\n-      if (change.getType() == nextChange.getType()\n-          && change.getLineNumber() == nextChange.getLineNumber()\n-          && change.getEndIndex() == nextChange.getStartIndex()) {\n-        // Merge changes\n-        result.set(\n-            i,\n-            change\n-                .toBuilder()\n-                .setText(change.getText() + nextChange.getText())\n-                .setEndIndex(nextChange.getEndIndex())\n-                .build());\n-        result.remove(i + 1);\n+  private TextDiff addWordChanges(TextDiff textDiff) {\n+    if (textDiff.getLeftDiffLineList().isEmpty()) {\n+      return textDiff;\n+    }\n+    TextDiff.Builder result = textDiff.toBuilder();\n+    result.clearLeftDiffLine();\n+    result.clearRightDiffLine();\n+    int segmentStart = 0;\n+    for (int diffIndex = 1; diffIndex < textDiff.getLeftDiffLineList().size(); diffIndex++) {\n+      DiffLine leftDiffLine = textDiff.getLeftDiffLineList().get(diffIndex);\n+      DiffLine prevLeftDiffLine = textDiff.getLeftDiffLineList().get(diffIndex);\n+      if (leftDiffLine.getDiffLineNumber() - prevLeftDiffLine.getDiffLineNumber() > 1) {\n+        String leftText = getMultilineText(textDiff.getLeftDiffLineList(), segmentStart, diffIndex);\n+        String rightText =\n+            getMultilineText(textDiff.getRightDiffLineList(), segmentStart, diffIndex);\n+        ImmutableList<DiffMatchPatch.Diff> diffs =\n+            splitMultiLines(getDiffPatchMatchDiff(leftText, rightText));\n+        for (DiffMatchPatch.Diff diff : diffs) {\n+          // TODO: Implement\n+        }\n+        segmentStart = diffIndex;\n+      }\n+    }\n+    // Take care of last segment\n+    if (segmentStart < textDiff.getLeftDiffLineList().size() - 1) {\n+      ImmutableList<DiffMatchPatch.Diff> diffs =\n+          splitMultiLines(\n+              getDiffPatchMatchDiff(\n+                  getMultilineText(\n+                      textDiff.getLeftDiffLineList(),\n+                      segmentStart,\n+                      textDiff.getLeftDiffLineList().size()),\n+                  getMultilineText(\n+                      textDiff.getRightDiffLineList(),\n+                      segmentStart,\n+                      textDiff.getRightDiffLineList().size())));\n+      for (int i = 0; i < diffs.size(); i++) {\n+        DiffMatchPatch.Diff diff = diffs.get(i);\n+        // TODO: Implement\n       }\n     }\n-    return ImmutableList.copyOf(result);\n+    return result.build();\n   }\n \n-  private TextChange getTextChange(\n-      String text, int lineNumber, int lineIndex, ChangeType changeType) {\n-    return TextChange.newBuilder()\n-        .setText(text)\n-        .setType(changeType)\n-        .setLineNumber(lineNumber)\n-        .setStartIndex(lineIndex - text.length())\n-        .setEndIndex(lineIndex)\n-        .build();\n+  private ImmutableList<DiffMatchPatch.Diff> splitMultiLines(List<DiffMatchPatch.Diff> diffs) {\n+    ImmutableList.Builder<DiffMatchPatch.Diff> result = ImmutableList.builder();\n+    for (DiffMatchPatch.Diff diff : diffs) {\n+      for (String line : diff.text.split(\"\\n\")) {\n+        if (!line.isEmpty()) {\n+          result.add(new DiffMatchPatch.Diff(diff.operation, line));\n+        }\n+      }\n+    }\n+    return result.build();\n   }\n \n-  public TextDiff getTextDiff(String leftContents, String rightContents) {\n-    if (leftContents.isEmpty() && rightContents.isEmpty()) {\n-      return TextDiff.getDefaultInstance();\n+  // endIndex is not inclusive - i.e, diffLines at endIndex is not used.\n+  private String getMultilineText(List<DiffLine> diffLines, int startIndex, int endIndex) {\n+    StringBuilder result = new StringBuilder();\n+    for (int i = startIndex; i < endIndex - 1; i++) {\n+      result.append(diffLines.get(i).getText() + \"\\n\");\n     }\n-    ImmutableList<DiffPatchMatchChange> changes =\n-        getDiffPatchMatchChanges(leftContents, rightContents);\n-\n-    TextDiff diff =\n-        TextDiff.newBuilder()\n-            .addAllLeftChange(getSingleSideChanges(changes, ChangeType.DELETE))\n-            .addAllRightChange(getSingleSideChanges(changes, ChangeType.ADD))\n-            .setLeftFileContents(leftContents)\n-            .setRightFileContents(rightContents)\n-            .build();\n-    System.out.println(\"*************** DIFF **************\");\n-    System.out.println(diff);\n-    return diff;\n+    // Last one without newline\n+    result.append(diffLines.get(endIndex - 1).getText());\n+    return result.toString();\n   }\n \n   private ChangeType getChangeType(Operation operation) {","path":"common/TextDifferencer.java","position":401,"original_position":401,"commit_id":"48064ec0be27ef67b12a67779ad1fe33125776e7","original_commit_id":"48064ec0be27ef67b12a67779ad1fe33125776e7","user":{"login":"oferb","id":5137671,"node_id":"MDQ6VXNlcjUxMzc2NzE=","avatar_url":"https://avatars1.githubusercontent.com/u/5137671?v=4","gravatar_id":"","url":"https://api.github.com/users/oferb","html_url":"https://github.com/oferb","followers_url":"https://api.github.com/users/oferb/followers","following_url":"https://api.github.com/users/oferb/following{/other_user}","gists_url":"https://api.github.com/users/oferb/gists{/gist_id}","starred_url":"https://api.github.com/users/oferb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oferb/subscriptions","organizations_url":"https://api.github.com/users/oferb/orgs","repos_url":"https://api.github.com/users/oferb/repos","events_url":"https://api.github.com/users/oferb/events{/privacy}","received_events_url":"https://api.github.com/users/oferb/received_events","type":"User","site_admin":false},"body":"Yes, we'll use it in the TODO to add `WordChanges`.","created_at":"2018-11-29T11:14:31Z","updated_at":"2018-11-29T11:14:31Z","html_url":"https://github.com/google/startup-os/pull/423#discussion_r237444532","pull_request_url":"https://api.github.com/repos/google/startup-os/pulls/423","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/google/startup-os/pulls/comments/237444532"},"html":{"href":"https://github.com/google/startup-os/pull/423#discussion_r237444532"},"pull_request":{"href":"https://api.github.com/repos/google/startup-os/pulls/423"}},"in_reply_to_id":237360459},"pull_request":{"url":"https://api.github.com/repos/google/startup-os/pulls/423","id":234409273,"node_id":"MDExOlB1bGxSZXF1ZXN0MjM0NDA5Mjcz","html_url":"https://github.com/google/startup-os/pull/423","diff_url":"https://github.com/google/startup-os/pull/423.diff","patch_url":"https://github.com/google/startup-os/pull/423.patch","issue_url":"https://api.github.com/repos/google/startup-os/issues/423","number":423,"state":"open","locked":false,"title":"D340: Use git's text diff for whole lines","user":{"login":"oferb","id":5137671,"node_id":"MDQ6VXNlcjUxMzc2NzE=","avatar_url":"https://avatars1.githubusercontent.com/u/5137671?v=4","gravatar_id":"","url":"https://api.github.com/users/oferb","html_url":"https://github.com/oferb","followers_url":"https://api.github.com/users/oferb/followers","following_url":"https://api.github.com/users/oferb/following{/other_user}","gists_url":"https://api.github.com/users/oferb/gists{/gist_id}","starred_url":"https://api.github.com/users/oferb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oferb/subscriptions","organizations_url":"https://api.github.com/users/oferb/orgs","repos_url":"https://api.github.com/users/oferb/repos","events_url":"https://api.github.com/users/oferb/events{/privacy}","received_events_url":"https://api.github.com/users/oferb/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-11-28T20:12:30Z","updated_at":"2018-11-29T11:14:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"93538e27ebbb34ff1ff37ee652bfe6eb9146a527","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/google/startup-os/pulls/423/commits","review_comments_url":"https://api.github.com/repos/google/startup-os/pulls/423/comments","review_comment_url":"https://api.github.com/repos/google/startup-os/pulls/comments{/number}","comments_url":"https://api.github.com/repos/google/startup-os/issues/423/comments","statuses_url":"https://api.github.com/repos/google/startup-os/statuses/48064ec0be27ef67b12a67779ad1fe33125776e7","head":{"label":"google:D340","ref":"D340","sha":"48064ec0be27ef67b12a67779ad1fe33125776e7","user":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":122602787,"node_id":"MDEwOlJlcG9zaXRvcnkxMjI2MDI3ODc=","name":"startup-os","full_name":"google/startup-os","private":false,"owner":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/google/startup-os","description":"Working examples of Google's Open Source stack and deployment to the cloud.","fork":false,"url":"https://api.github.com/repos/google/startup-os","forks_url":"https://api.github.com/repos/google/startup-os/forks","keys_url":"https://api.github.com/repos/google/startup-os/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/startup-os/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/startup-os/teams","hooks_url":"https://api.github.com/repos/google/startup-os/hooks","issue_events_url":"https://api.github.com/repos/google/startup-os/issues/events{/number}","events_url":"https://api.github.com/repos/google/startup-os/events","assignees_url":"https://api.github.com/repos/google/startup-os/assignees{/user}","branches_url":"https://api.github.com/repos/google/startup-os/branches{/branch}","tags_url":"https://api.github.com/repos/google/startup-os/tags","blobs_url":"https://api.github.com/repos/google/startup-os/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/startup-os/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/startup-os/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/startup-os/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/startup-os/statuses/{sha}","languages_url":"https://api.github.com/repos/google/startup-os/languages","stargazers_url":"https://api.github.com/repos/google/startup-os/stargazers","contributors_url":"https://api.github.com/repos/google/startup-os/contributors","subscribers_url":"https://api.github.com/repos/google/startup-os/subscribers","subscription_url":"https://api.github.com/repos/google/startup-os/subscription","commits_url":"https://api.github.com/repos/google/startup-os/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/startup-os/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/startup-os/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/startup-os/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/startup-os/contents/{+path}","compare_url":"https://api.github.com/repos/google/startup-os/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/startup-os/merges","archive_url":"https://api.github.com/repos/google/startup-os/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/startup-os/downloads","issues_url":"https://api.github.com/repos/google/startup-os/issues{/number}","pulls_url":"https://api.github.com/repos/google/startup-os/pulls{/number}","milestones_url":"https://api.github.com/repos/google/startup-os/milestones{/number}","notifications_url":"https://api.github.com/repos/google/startup-os/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/startup-os/labels{/name}","releases_url":"https://api.github.com/repos/google/startup-os/releases{/id}","deployments_url":"https://api.github.com/repos/google/startup-os/deployments","created_at":"2018-02-23T09:48:29Z","updated_at":"2018-11-28T21:20:09Z","pushed_at":"2018-11-29T07:36:27Z","git_url":"git://github.com/google/startup-os.git","ssh_url":"git@github.com:google/startup-os.git","clone_url":"https://github.com/google/startup-os.git","svn_url":"https://github.com/google/startup-os","homepage":"","size":2413,"stargazers_count":217,"watchers_count":217,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":51,"mirror_url":null,"archived":false,"open_issues_count":48,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":51,"open_issues":48,"watchers":217,"default_branch":"master"}},"base":{"label":"google:master","ref":"master","sha":"e9749bce02337a1d25cfa90a71a727bb79d66bb6","user":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":122602787,"node_id":"MDEwOlJlcG9zaXRvcnkxMjI2MDI3ODc=","name":"startup-os","full_name":"google/startup-os","private":false,"owner":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/google/startup-os","description":"Working examples of Google's Open Source stack and deployment to the cloud.","fork":false,"url":"https://api.github.com/repos/google/startup-os","forks_url":"https://api.github.com/repos/google/startup-os/forks","keys_url":"https://api.github.com/repos/google/startup-os/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/startup-os/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/startup-os/teams","hooks_url":"https://api.github.com/repos/google/startup-os/hooks","issue_events_url":"https://api.github.com/repos/google/startup-os/issues/events{/number}","events_url":"https://api.github.com/repos/google/startup-os/events","assignees_url":"https://api.github.com/repos/google/startup-os/assignees{/user}","branches_url":"https://api.github.com/repos/google/startup-os/branches{/branch}","tags_url":"https://api.github.com/repos/google/startup-os/tags","blobs_url":"https://api.github.com/repos/google/startup-os/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/startup-os/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/startup-os/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/startup-os/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/startup-os/statuses/{sha}","languages_url":"https://api.github.com/repos/google/startup-os/languages","stargazers_url":"https://api.github.com/repos/google/startup-os/stargazers","contributors_url":"https://api.github.com/repos/google/startup-os/contributors","subscribers_url":"https://api.github.com/repos/google/startup-os/subscribers","subscription_url":"https://api.github.com/repos/google/startup-os/subscription","commits_url":"https://api.github.com/repos/google/startup-os/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/startup-os/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/startup-os/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/startup-os/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/startup-os/contents/{+path}","compare_url":"https://api.github.com/repos/google/startup-os/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/startup-os/merges","archive_url":"https://api.github.com/repos/google/startup-os/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/startup-os/downloads","issues_url":"https://api.github.com/repos/google/startup-os/issues{/number}","pulls_url":"https://api.github.com/repos/google/startup-os/pulls{/number}","milestones_url":"https://api.github.com/repos/google/startup-os/milestones{/number}","notifications_url":"https://api.github.com/repos/google/startup-os/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/startup-os/labels{/name}","releases_url":"https://api.github.com/repos/google/startup-os/releases{/id}","deployments_url":"https://api.github.com/repos/google/startup-os/deployments","created_at":"2018-02-23T09:48:29Z","updated_at":"2018-11-28T21:20:09Z","pushed_at":"2018-11-29T07:36:27Z","git_url":"git://github.com/google/startup-os.git","ssh_url":"git@github.com:google/startup-os.git","clone_url":"https://github.com/google/startup-os.git","svn_url":"https://github.com/google/startup-os","homepage":"","size":2413,"stargazers_count":217,"watchers_count":217,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":51,"mirror_url":null,"archived":false,"open_issues_count":48,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":51,"open_issues":48,"watchers":217,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/google/startup-os/pulls/423"},"html":{"href":"https://github.com/google/startup-os/pull/423"},"issue":{"href":"https://api.github.com/repos/google/startup-os/issues/423"},"comments":{"href":"https://api.github.com/repos/google/startup-os/issues/423/comments"},"review_comments":{"href":"https://api.github.com/repos/google/startup-os/pulls/423/comments"},"review_comment":{"href":"https://api.github.com/repos/google/startup-os/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/google/startup-os/pulls/423/commits"},"statuses":{"href":"https://api.github.com/repos/google/startup-os/statuses/48064ec0be27ef67b12a67779ad1fe33125776e7"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"oferb"}}