build: Add GitHub scripts for rebasing PRs (#18359)

PR Close #18359
This commit is contained in:
Miško Hevery 2017-07-26 12:37:14 -07:00 committed by Miško Hevery
parent 2e714f9f2a
commit d1764fc3dd
10 changed files with 706 additions and 2 deletions

92
docs/CARETAKER.md Normal file
View File

@ -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
```

View File

@ -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?

90
scripts/github/merge-pr Executable file
View File

@ -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 <<<<<<"

12
scripts/github/push-upstream Executable file
View File

@ -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

45
scripts/github/rebase-pr Executable file
View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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']);

View File

@ -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
}
]

View File

@ -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<!-- Please check the one that applies to this PR using \"x\". -->\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
}