diff --git a/docs/CARETAKER.md b/docs/CARETAKER.md new file mode 100644 index 0000000000..fbd44dd9e4 --- /dev/null +++ b/docs/CARETAKER.md @@ -0,0 +1,92 @@ +# Caretaker + +Caretaker is responsible for merging PRs into the individual branches and internally at Google. + +## Responsibilities + +- Draining the queue of PRs ready to be merged. (PRs with [`PR action: merge`](https://github.com/angular/angular/pulls?q=is%3Aopen+is%3Apr+label%3A%22PR+action%3A+merge%22) label) +- Assigining [new issues](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) to individual component authors. + +## Setup + +### Set `upstream` to fetch PRs into your local repo + +Use this conmmands to configure your `git` to fetch PRs into your local repo. + +``` +git remote add upstream git@github.com:angular/angular.git +git config --add remote.upstream.fetch +refs/pull/*/head:refs/remotes/upstream/pr/* +``` + + +## Merging the PR + +A PR needs to have `PR action: merge` and `PR target: *` labels to be considered +ready to merge. Merging is performed by running `merge-pr` with a PR number to merge. + +NOTE: before running `merge-pr` ensure that you have synced all of the PRs +locally by running: + +``` +$ git fetch upstream +``` + +To merge a PR run: + +``` +$ ./scripts/github/merge-pr 1234 +``` + +The `merge-pr` script will: +- Ensure that all approriate labels are on the PR. +- That the current branch (`master` or `?.?.x` patch) mathches the `PR target: *` label. +- It will `cherry-pick` all of the SHAs from the PR into the current branch. +- It will rewrite commit history by automatically adding `Close #1234` and `(#1234)` into the commit message. + + +### Recovering from failed `merge-pr` due to conflicts + +When running `merge-pr` the script will output the commands which it is about to run. + +``` +$ ./scripts/github/merge-pr 1234 +====================== +GitHub Merge PR Steps +====================== + git cherry-pick upstream/pr/1234~1..upstream/pr/1234 + git filter-branch -f --msg-filter "/usr/local/google/home/misko/angular-pr/scripts/github/utils/github.closes 1234" HEAD~1..HEAD +``` + +If the `cherry-pick` command fails than resolve conflicts and use `git cherry-pick --continue` once ready. After the `cherry-pick` is done cut&paste and run the `filter-branch` command to properly rewrite the messages + +## Cherry-picking PRs into patch branch + +In addition to merging PRs into the master branch, many PRs need to be also merged into a patch branch. +Follow these steps to get path brach up to date. + +1. Check out the most recent patch branch: `git checkout 4.3.x` +2. Get a list of PRs merged into master: `git log master --oneline -n10` +3. For each PR number in the commit message run: `././scripts/github/merge-pr 1234` + - The PR will only merge if the `PR target:` matches the branch. + +Once all of the PRs are in patch branch, push the all branches and tags to github using `push-upstream` script. + + +## Pushing merged PRs into github + +Use `push-upstream` script to push all of the branch and tags to github. + +``` +$ ./scripts/github/push-upstream +git push git@github.com:angular/angular.git master:master 4.3.x:4.3.x +Counting objects: 25, done. +Delta compression using up to 6 threads. +Compressing objects: 100% (17/17), done. +Writing objects: 100% (25/25), 2.22 KiB | 284.00 KiB/s, done. +Total 25 (delta 22), reused 8 (delta 7) +remote: Resolving deltas: 100% (22/22), completed with 18 local objects. +To github.com:angular/angular.git + 079d884b6..d1c4a94bb master -> master +git push --tags -f git@github.com:angular/angular.git patch_sync:patch_sync +Everything up-to-date +``` \ No newline at end of file diff --git a/docs/COMMITTER.md b/docs/COMMITTER.md index b4f293ca2c..6592bc6b3e 100644 --- a/docs/COMMITTER.md +++ b/docs/COMMITTER.md @@ -13,8 +13,8 @@ Change approvals in our monorepo are managed via [pullapprove.com](https://about # Merging -Once a change has all the approvals either the last approver or the PR author (if PR author has the project collaborator status) should mark the PR with "PR: merge" label. -This signals to the caretaker that the PR should be merged. +Once a change has all the approvals either the last approver or the PR author (if PR author has the project collaborator status) should mark the PR with `PR: merge` as well as `PR target: *` labels. +This signals to the caretaker that the PR should be merged. See [merge instructions](../CARETAKER.md). # Who is the Caretaker? diff --git a/scripts/github/merge-pr b/scripts/github/merge-pr new file mode 100755 index 0000000000..08233759e5 --- /dev/null +++ b/scripts/github/merge-pr @@ -0,0 +1,90 @@ +#!/bin/sh + +set -u -e -o pipefail + +BASEDIR=$(dirname "$0") +BASEDIR=`(cd $BASEDIR; pwd)` + +if [ $# -eq 0 ]; then + echo "Merge github PR into the current branch" + echo + echo "$0 PR_NUMBER" + echo + exit 0 +fi + +PR_NUMBER="$1" +CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD` +PR_SHA_COUNT=`curl -s https://api.github.com/repos/angular/angular/pulls/$PR_NUMBER | node $BASEDIR/utils/json_extract.js commits` +PR_LABELS=`curl -s https://api.github.com/repos/angular/angular/issues/$PR_NUMBER/labels` +PR_ACTION=`echo "$PR_LABELS" | node $BASEDIR/utils/json_extract.js "name=^PR action:"` +PR_TARGET=`echo "$PR_LABELS" | node $BASEDIR/utils/json_extract.js "name=^PR target:"` +PR_CLA=`echo "$PR_LABELS" | node $BASEDIR/utils/json_extract.js "name=^cla"` +MASTER_BRANCH='master' +SHA=`git rev-parse HEAD` +PATCH_BRANCH=`git branch --list '*.x' | cut -d ' ' -f2- | sort -r | head -n1` +# Trim whitespace +PATCH_BRANCH=`echo $PATCH_BRANCH` + +if [[ "$PR_ACTION" != "PR action: merge" ]]; then + echo The PR is missing 'PR action: merge' label, found: $PR_ACTION + exit 1 +fi + +if [[ "$PR_CLA" != "cla: yes" ]]; then + echo The PR is missing 'cla: Yes' label, found: $PR_CLA + exit 1 +fi + + +if [[ $PR_TARGET == "PR target: master & patch" ]]; then + MERGE_MASTER=1 + MERGE_PATCH=1 +elif [[ $PR_TARGET == "PR target: master-only" ]]; then + MERGE_MASTER=1 + MERGE_PATCH=0 +elif [[ $PR_TARGET == "PR target: patch-only" ]]; then + MERGE_MASTER=0 + MERGE_PATCH=1 +else + echo "Unknown PR target format: $PR_TARGET" + exit 1; +fi + +if [[ $CURRENT_BRANCH == $MASTER_BRANCH ]]; then + if [[ $MERGE_MASTER == 0 ]]; then + echo "This PR is not intended for master branch: $PR_TARGET" + exit 1 + fi +elif [[ $CURRENT_BRANCH == $PATCH_BRANCH ]]; then + if [[ $MERGE_PATCH == 0 ]]; then + echo "This PR is not intended for patch branch: $PR_TARGET" + exit 1 + fi +else + echo "Current branch $CURRENT_BRANCH does not match $MASTER_BRANCH or $PATCH_BRANCH." + exit 1 +fi + + +CHERRY_PICK_PR="git cherry-pick upstream/pr/$PR_NUMBER~$PR_SHA_COUNT..upstream/pr/$PR_NUMBER" +REWRITE_MESSAGE="git filter-branch -f --msg-filter \"$BASEDIR/utils/github_closes.js $PR_NUMBER\" HEAD~$PR_SHA_COUNT..HEAD" + +echo "======================" +echo "GitHub Merge PR Steps" +echo "======================" +echo " $CHERRY_PICK_PR" +echo " $REWRITE_MESSAGE" +echo "----------------------" + +echo ">>> Cherry Pick: $CHERRY_PICK_PR" +$CHERRY_PICK_PR + +echo +echo ">>> Rewrite Messages: $REWRITE_MESSAGE" +# Next line should work, but it errors, hence copy paste the command. +# $REWRITE_MESSAGE +git filter-branch -f --msg-filter "$BASEDIR/utils/github_closes.js $PR_NUMBER" HEAD~$PR_SHA_COUNT..HEAD + +echo +echo ">>>>>> SUCCESS <<<<<<" diff --git a/scripts/github/push-upstream b/scripts/github/push-upstream new file mode 100755 index 0000000000..076ffcc856 --- /dev/null +++ b/scripts/github/push-upstream @@ -0,0 +1,12 @@ +#!/bin/sh + +set -u -e -o pipefail + +PATCH_BRANCH=`git branch --list '*.x' | cut -d ' ' -f2- | sort -r | head -n1` +# Trim whitespace +PATCH_BRANCH=`echo $PATCH_BRANCH` + +PUSH_BRANCHES="git push git@github.com:angular/angular.git master:master $PATCH_BRANCH:$PATCH_BRANCH" + +echo $PUSH_BRANCHES +$PUSH_BRANCHES diff --git a/scripts/github/rebase-pr b/scripts/github/rebase-pr new file mode 100755 index 0000000000..77cda6cd51 --- /dev/null +++ b/scripts/github/rebase-pr @@ -0,0 +1,45 @@ +#!/bin/sh + +set -u -e -o pipefail + +BASEDIR=$(dirname "$0") + +if [ $# -eq 0 ]; then + echo "Rebase github PR onto a branch" + echo + echo "$0 branch_to_rebase_on PR_NUMBER" + echo + exit 0 +fi + +REBASE_ON="$1" +PR_NO="$2" + + +HEAD_LABEL=`curl -s https://api.github.com/repos/angular/angular/pulls/$PR_NO | node $BASEDIR/utils/json_extract.js head.label` +echo $HEAD_LABEL +IFS=':' read -r -a array <<< "$HEAD_LABEL" +USER="${array[0]}" +USER_GIT_URL="git@github.com:$USER/angular.git" +BRANCH="${array[1]}" +OLD_BRANCH=`git branch | grep \* | cut -d ' ' -f2` + +echo ===================================================== +echo Rebasing $USER_GIT_URL branch $BRANCH onto $REBASE_ON +echo ===================================================== + +git fetch $USER_GIT_URL $BRANCH +git co FETCH_HEAD +PUSH_CMD="git push $USER_GIT_URL HEAD:$BRANCH -f"; +RESTORE_CMD="git co $OLD_BRANCH" +git rebase upstream/master +if [ $? -eq 0 ]; then + $PUSH_CMD + $RESTORE_CMD +else + echo =========================== + echo Git rebase failed. RECOVER WITH: + echo " $PUSH_CMD" + echo " $RESTORE_CMD" + echo =========================== +fi diff --git a/scripts/github/utils/github_closes.js b/scripts/github/utils/github_closes.js new file mode 100755 index 0000000000..f601541826 --- /dev/null +++ b/scripts/github/utils/github_closes.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +var msg = ''; + +if (require.main === module) { + process.stdin.setEncoding('utf8'); + + process.stdin.on('readable', () => { + const chunk = process.stdin.read(); + if (chunk !== null) { + msg += chunk; + } + }); + + process.stdin.on('end', () => { + var argv = process.argv.slice(2); + console.log(rewriteMsg(msg, argv[0])); + }); +} + +function rewriteMsg(msg, prNo) { + var lines = msg.split(/\n/); + lines[0] += ' (#' + prNo +')'; + lines.push('PR Close #' + prNo); + return lines.join('\n'); +} + +exports.rewriteMsg = rewriteMsg; \ No newline at end of file diff --git a/scripts/github/utils/json_extract.js b/scripts/github/utils/json_extract.js new file mode 100644 index 0000000000..4de803a187 --- /dev/null +++ b/scripts/github/utils/json_extract.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +var json = ''; + +if (require.main === module) { + process.stdin.setEncoding('utf8'); + + process.stdin.on('readable', () => { + const chunk = process.stdin.read(); + if (chunk !== null) { + json += chunk; + } + }); + + process.stdin.on('end', () => { + var obj = JSON.parse(json); + var argv = process.argv.slice(2); + extractPaths(obj, argv).forEach(function(line) { + console.log(line); + }) + }); +} + +function extractPaths(obj, paths) { + var lines = []; + paths.forEach(function(exp) { + var objs = obj instanceof Array ? [].concat(obj) : [obj]; + exp.split('.').forEach(function(name) { + for(var i = 0; i < objs.length; i++) { + var o = objs[i]; + if (o instanceof Array) { + // Expand and do over + objs = objs.slice(0, i).concat(o).concat(objs.slice(i+1, objs.length)); + i--; + } else { + name.split("=").forEach(function(name, index) { + if (index == 0) { + objs[i] = o = o[name]; + } else if (name.charAt(0) == '^') { + if (o.indexOf(name.substr(1)) !== 0) { + objs.splice(i, 1); + i--; + } + } + }); + } + } + }); + lines.push(objs.join("|")); + }); + return lines; +} + +exports.extractPaths = extractPaths; \ No newline at end of file diff --git a/scripts/github/utils/json_extract_test.js b/scripts/github/utils/json_extract_test.js new file mode 100644 index 0000000000..fb00fabd79 --- /dev/null +++ b/scripts/github/utils/json_extract_test.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +var assert = require("assert"); +var extractPaths = require('./json_extract').extractPaths; + +var SAMPLE_LABELS = [ + { + "id": 149476251, + "url": "https://api.github.com/repos/angular/angular/labels/cla:%20yes", + "name": "cla: yes", + "color": "009800", + "default": false + }, + { + "id": 533874619, + "url": "https://api.github.com/repos/angular/angular/labels/comp:%20aio", + "name": "comp: aio", + "color": "c7def8", + "default": false + }, + { + "id": 133556520, + "url": "https://api.github.com/repos/angular/angular/labels/PR%20action:%20merge", + "name": "PR action: merge", + "color": "99ff66", + "default": false + }, + { + "id": 655699838, + "url": "https://api.github.com/repos/angular/angular/labels/PR%20target:%20master%20&%20patch", + "name": "PR target: master & patch", + "color": "5319e7", + "default": false + } +]; + +assert.deepEqual(extractPaths({head: {label: 'value1'}}, ['head.label']), ['value1']); +assert.deepEqual(extractPaths(SAMPLE_LABELS, ['name']), ['cla: yes|comp: aio|PR action: merge|PR target: master & patch']); +assert.deepEqual(extractPaths(SAMPLE_LABELS, ['name=^PR target:']), ['PR target: master & patch']); diff --git a/scripts/github/utils/labels.json b/scripts/github/utils/labels.json new file mode 100644 index 0000000000..7a6384d11e --- /dev/null +++ b/scripts/github/utils/labels.json @@ -0,0 +1,30 @@ +[ + { + "id": 149476251, + "url": "https://api.github.com/repos/angular/angular/labels/cla:%20yes", + "name": "cla: yes", + "color": "009800", + "default": false + }, + { + "id": 533874619, + "url": "https://api.github.com/repos/angular/angular/labels/comp:%20aio", + "name": "comp: aio", + "color": "c7def8", + "default": false + }, + { + "id": 133556520, + "url": "https://api.github.com/repos/angular/angular/labels/PR%20action:%20merge", + "name": "PR action: merge", + "color": "99ff66", + "default": false + }, + { + "id": 655699838, + "url": "https://api.github.com/repos/angular/angular/labels/PR%20target:%20master%20&%20patch", + "name": "PR target: master & patch", + "color": "5319e7", + "default": false + } +] diff --git a/scripts/github/utils/pulls.json b/scripts/github/utils/pulls.json new file mode 100644 index 0000000000..6233e4c680 --- /dev/null +++ b/scripts/github/utils/pulls.json @@ -0,0 +1,314 @@ +{ + "url": "https://api.github.com/repos/angular/angular/pulls/18730", + "id": 136020267, + "html_url": "https://github.com/angular/angular/pull/18730", + "diff_url": "https://github.com/angular/angular/pull/18730.diff", + "patch_url": "https://github.com/angular/angular/pull/18730.patch", + "issue_url": "https://api.github.com/repos/angular/angular/issues/18730", + "number": 18730, + "state": "open", + "locked": false, + "title": "docs(aio): typo in metadata guide", + "user": { + "login": "cexbrayat", + "id": 411874, + "avatar_url": "https://avatars3.githubusercontent.com/u/411874?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cexbrayat", + "html_url": "https://github.com/cexbrayat", + "followers_url": "https://api.github.com/users/cexbrayat/followers", + "following_url": "https://api.github.com/users/cexbrayat/following{/other_user}", + "gists_url": "https://api.github.com/users/cexbrayat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cexbrayat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cexbrayat/subscriptions", + "organizations_url": "https://api.github.com/users/cexbrayat/orgs", + "repos_url": "https://api.github.com/users/cexbrayat/repos", + "events_url": "https://api.github.com/users/cexbrayat/events{/privacy}", + "received_events_url": "https://api.github.com/users/cexbrayat/received_events", + "type": "User", + "site_admin": false + }, + "body": "## PR Type\r\n\r\nSimple typo fix in aio guide\r\n\r\n\r\n```\r\n[ ] Bugfix\r\n[ ] Feature\r\n[ ] Code style update (formatting, local variables)\r\n[ ] Refactoring (no functional changes, no api changes)\r\n[ ] Build related changes\r\n[ ] CI related changes\r\n[x] Documentation content changes\r\n[ ] angular.io application / infrastructure changes\r\n[ ] Other... Please describe:\r\n```", + "created_at": "2017-08-16T13:11:04Z", + "updated_at": "2017-08-17T13:59:21Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "35f8a4fdd7b37d41d8ed71c0f9978ed3e751da43", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "milestone": null, + "commits_url": "https://api.github.com/repos/angular/angular/pulls/18730/commits", + "review_comments_url": "https://api.github.com/repos/angular/angular/pulls/18730/comments", + "review_comment_url": "https://api.github.com/repos/angular/angular/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/angular/angular/issues/18730/comments", + "statuses_url": "https://api.github.com/repos/angular/angular/statuses/d59a5eec6cc60e301033300957f0c959b8056650", + "head": { + "label": "cexbrayat:docs/metadata-guide", + "ref": "docs/metadata-guide", + "sha": "d59a5eec6cc60e301033300957f0c959b8056650", + "user": { + "login": "cexbrayat", + "id": 411874, + "avatar_url": "https://avatars3.githubusercontent.com/u/411874?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cexbrayat", + "html_url": "https://github.com/cexbrayat", + "followers_url": "https://api.github.com/users/cexbrayat/followers", + "following_url": "https://api.github.com/users/cexbrayat/following{/other_user}", + "gists_url": "https://api.github.com/users/cexbrayat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cexbrayat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cexbrayat/subscriptions", + "organizations_url": "https://api.github.com/users/cexbrayat/orgs", + "repos_url": "https://api.github.com/users/cexbrayat/repos", + "events_url": "https://api.github.com/users/cexbrayat/events{/privacy}", + "received_events_url": "https://api.github.com/users/cexbrayat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 25485740, + "name": "angular", + "full_name": "cexbrayat/angular", + "owner": { + "login": "cexbrayat", + "id": 411874, + "avatar_url": "https://avatars3.githubusercontent.com/u/411874?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cexbrayat", + "html_url": "https://github.com/cexbrayat", + "followers_url": "https://api.github.com/users/cexbrayat/followers", + "following_url": "https://api.github.com/users/cexbrayat/following{/other_user}", + "gists_url": "https://api.github.com/users/cexbrayat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cexbrayat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cexbrayat/subscriptions", + "organizations_url": "https://api.github.com/users/cexbrayat/orgs", + "repos_url": "https://api.github.com/users/cexbrayat/repos", + "events_url": "https://api.github.com/users/cexbrayat/events{/privacy}", + "received_events_url": "https://api.github.com/users/cexbrayat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/cexbrayat/angular", + "description": null, + "fork": true, + "url": "https://api.github.com/repos/cexbrayat/angular", + "forks_url": "https://api.github.com/repos/cexbrayat/angular/forks", + "keys_url": "https://api.github.com/repos/cexbrayat/angular/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/cexbrayat/angular/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/cexbrayat/angular/teams", + "hooks_url": "https://api.github.com/repos/cexbrayat/angular/hooks", + "issue_events_url": "https://api.github.com/repos/cexbrayat/angular/issues/events{/number}", + "events_url": "https://api.github.com/repos/cexbrayat/angular/events", + "assignees_url": "https://api.github.com/repos/cexbrayat/angular/assignees{/user}", + "branches_url": "https://api.github.com/repos/cexbrayat/angular/branches{/branch}", + "tags_url": "https://api.github.com/repos/cexbrayat/angular/tags", + "blobs_url": "https://api.github.com/repos/cexbrayat/angular/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/cexbrayat/angular/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/cexbrayat/angular/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/cexbrayat/angular/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/cexbrayat/angular/statuses/{sha}", + "languages_url": "https://api.github.com/repos/cexbrayat/angular/languages", + "stargazers_url": "https://api.github.com/repos/cexbrayat/angular/stargazers", + "contributors_url": "https://api.github.com/repos/cexbrayat/angular/contributors", + "subscribers_url": "https://api.github.com/repos/cexbrayat/angular/subscribers", + "subscription_url": "https://api.github.com/repos/cexbrayat/angular/subscription", + "commits_url": "https://api.github.com/repos/cexbrayat/angular/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/cexbrayat/angular/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/cexbrayat/angular/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/cexbrayat/angular/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/cexbrayat/angular/contents/{+path}", + "compare_url": "https://api.github.com/repos/cexbrayat/angular/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/cexbrayat/angular/merges", + "archive_url": "https://api.github.com/repos/cexbrayat/angular/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/cexbrayat/angular/downloads", + "issues_url": "https://api.github.com/repos/cexbrayat/angular/issues{/number}", + "pulls_url": "https://api.github.com/repos/cexbrayat/angular/pulls{/number}", + "milestones_url": "https://api.github.com/repos/cexbrayat/angular/milestones{/number}", + "notifications_url": "https://api.github.com/repos/cexbrayat/angular/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/cexbrayat/angular/labels{/name}", + "releases_url": "https://api.github.com/repos/cexbrayat/angular/releases{/id}", + "deployments_url": "https://api.github.com/repos/cexbrayat/angular/deployments", + "created_at": "2014-10-20T20:42:42Z", + "updated_at": "2017-07-03T09:58:51Z", + "pushed_at": "2017-08-16T13:20:57Z", + "git_url": "git://github.com/cexbrayat/angular.git", + "ssh_url": "git@github.com:cexbrayat/angular.git", + "clone_url": "https://github.com/cexbrayat/angular.git", + "svn_url": "https://github.com/cexbrayat/angular", + "homepage": null, + "size": 62846, + "stargazers_count": 1, + "watchers_count": 1, + "language": "TypeScript", + "has_issues": false, + "has_projects": true, + "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": 1, + "default_branch": "master" + } + }, + "base": { + "label": "angular:master", + "ref": "master", + "sha": "32ff21c16bf1833d2da9f8e2ec8536f7a13f92de", + "user": { + "login": "angular", + "id": 139426, + "avatar_url": "https://avatars3.githubusercontent.com/u/139426?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/angular", + "html_url": "https://github.com/angular", + "followers_url": "https://api.github.com/users/angular/followers", + "following_url": "https://api.github.com/users/angular/following{/other_user}", + "gists_url": "https://api.github.com/users/angular/gists{/gist_id}", + "starred_url": "https://api.github.com/users/angular/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/angular/subscriptions", + "organizations_url": "https://api.github.com/users/angular/orgs", + "repos_url": "https://api.github.com/users/angular/repos", + "events_url": "https://api.github.com/users/angular/events{/privacy}", + "received_events_url": "https://api.github.com/users/angular/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 24195339, + "name": "angular", + "full_name": "angular/angular", + "owner": { + "login": "angular", + "id": 139426, + "avatar_url": "https://avatars3.githubusercontent.com/u/139426?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/angular", + "html_url": "https://github.com/angular", + "followers_url": "https://api.github.com/users/angular/followers", + "following_url": "https://api.github.com/users/angular/following{/other_user}", + "gists_url": "https://api.github.com/users/angular/gists{/gist_id}", + "starred_url": "https://api.github.com/users/angular/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/angular/subscriptions", + "organizations_url": "https://api.github.com/users/angular/orgs", + "repos_url": "https://api.github.com/users/angular/repos", + "events_url": "https://api.github.com/users/angular/events{/privacy}", + "received_events_url": "https://api.github.com/users/angular/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/angular/angular", + "description": "One framework. Mobile & desktop.", + "fork": false, + "url": "https://api.github.com/repos/angular/angular", + "forks_url": "https://api.github.com/repos/angular/angular/forks", + "keys_url": "https://api.github.com/repos/angular/angular/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/angular/angular/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/angular/angular/teams", + "hooks_url": "https://api.github.com/repos/angular/angular/hooks", + "issue_events_url": "https://api.github.com/repos/angular/angular/issues/events{/number}", + "events_url": "https://api.github.com/repos/angular/angular/events", + "assignees_url": "https://api.github.com/repos/angular/angular/assignees{/user}", + "branches_url": "https://api.github.com/repos/angular/angular/branches{/branch}", + "tags_url": "https://api.github.com/repos/angular/angular/tags", + "blobs_url": "https://api.github.com/repos/angular/angular/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/angular/angular/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/angular/angular/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/angular/angular/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/angular/angular/statuses/{sha}", + "languages_url": "https://api.github.com/repos/angular/angular/languages", + "stargazers_url": "https://api.github.com/repos/angular/angular/stargazers", + "contributors_url": "https://api.github.com/repos/angular/angular/contributors", + "subscribers_url": "https://api.github.com/repos/angular/angular/subscribers", + "subscription_url": "https://api.github.com/repos/angular/angular/subscription", + "commits_url": "https://api.github.com/repos/angular/angular/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/angular/angular/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/angular/angular/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/angular/angular/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/angular/angular/contents/{+path}", + "compare_url": "https://api.github.com/repos/angular/angular/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/angular/angular/merges", + "archive_url": "https://api.github.com/repos/angular/angular/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/angular/angular/downloads", + "issues_url": "https://api.github.com/repos/angular/angular/issues{/number}", + "pulls_url": "https://api.github.com/repos/angular/angular/pulls{/number}", + "milestones_url": "https://api.github.com/repos/angular/angular/milestones{/number}", + "notifications_url": "https://api.github.com/repos/angular/angular/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/angular/angular/labels{/name}", + "releases_url": "https://api.github.com/repos/angular/angular/releases{/id}", + "deployments_url": "https://api.github.com/repos/angular/angular/deployments", + "created_at": "2014-09-18T16:12:01Z", + "updated_at": "2017-08-17T16:52:26Z", + "pushed_at": "2017-08-17T16:44:18Z", + "git_url": "git://github.com/angular/angular.git", + "ssh_url": "git@github.com:angular/angular.git", + "clone_url": "https://github.com/angular/angular.git", + "svn_url": "https://github.com/angular/angular", + "homepage": "https://angular.io", + "size": 64855, + "stargazers_count": 26993, + "watchers_count": 26993, + "language": "TypeScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 6796, + "mirror_url": null, + "open_issues_count": 1860, + "forks": 6796, + "open_issues": 1860, + "watchers": 26993, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/angular/angular/pulls/18730" + }, + "html": { + "href": "https://github.com/angular/angular/pull/18730" + }, + "issue": { + "href": "https://api.github.com/repos/angular/angular/issues/18730" + }, + "comments": { + "href": "https://api.github.com/repos/angular/angular/issues/18730/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/angular/angular/pulls/18730/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/angular/angular/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/angular/angular/pulls/18730/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/angular/angular/statuses/d59a5eec6cc60e301033300957f0c959b8056650" + } + }, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 0, + "review_comments": 2, + "maintainer_can_modify": true, + "commits": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1 +}