-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pulley for upstream jQuery; this is for my own benefit
- Loading branch information
timmywil
committed
Apr 28, 2011
0 parents
commit ab11a8c
Showing
5 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,19 @@ | |||
Copyright (c) 2011 John Resig | |||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
|
|||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,21 @@ | |||
Pulley: An Easy Github Pull Request Lander | |||
========================================== | |||
|
|||
Landing a pull request from Github can be annoying. You can follow the instructions provided by Github (pulling the code, doing a merge) but that'll result in a messy commit stream and external ticket trackers that don't automatically close tickets. | |||
|
|||
Additionally you can pull the code and squash it down into a single commit, which lets you format the commit nicely (closing tickets on external trackers) - but it fails to properly close the pull request. | |||
|
|||
Pulley is a tool that uses the best aspects of both techniques. Pull requests are pulled and merged into your project. The code is then squashed down into a single commit and nicely formatted with appropriate bug numbers and links. Finally the commit is pushed and the pull request is closed with a link to the commit. | |||
|
|||
Pulley is written using Node.js - thus you'll need to make sure that you have Node installed prior to running it. | |||
|
|||
How to use: | |||
|
|||
Start by configuring the repo and bug tracker details in the pulley.js file. Once that's complete you can run the following command: | |||
|
|||
node pulley.js PID # Where PID is the Pull Request ID | |||
|
|||
For example running the command `node pulley.js 332` on the jQuery repo yielded the following closed pull request and commit: | |||
|
|||
- https://github.com/jquery/jquery/pull/332 | |||
- https://github.com/jquery/jquery/commit/d274b7b9f7727e8bccd6906d954e4dc790404d23 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,13 @@ | |||
({ | |||
// Use the follow to specify custom bug tracker URLs | |||
repos: { | |||
"jquery/jquery": "http://bugs.jquery.com/ticket/" | |||
}, | |||
// You can specify these inline or in the Git config | |||
// http://help.github.com/git-email-settings/ | |||
gitconfig: { | |||
user: "", | |||
token: "" | |||
} | |||
}) | |||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,17 @@ | |||
{ | |||
"name": "pulley", | |||
"description": "Easy Github Pull Request Lander", | |||
"keywords": [ "github", "git", "pull" ], | |||
"version": "0.1.5", | |||
"homepage": "https://github.com/jeresig/pulley", | |||
"author": "John Resig (http://ejohn.org/)", | |||
"bin": { "pulley": "./pulley.js" }, | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/jeresig/pulley.git" | |||
}, | |||
"licenses": [{ | |||
"type": "MIT", | |||
"url": "https://github.com/jeresig/pulley/blob/master/LICENSE" | |||
}] | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,290 @@ | |||
#!/usr/bin/env node | |||
/* | |||
* Pulley: Easy Github Pull Request Lander | |||
* Copyright 2011 John Resig | |||
* MIT Licensed | |||
*/ | |||
|
|||
var // Application requirements | |||
child = require( "child_process" ), | |||
http = require( "https" ), | |||
fs = require( "fs" ), | |||
|
|||
// Process references | |||
exec = child.exec, | |||
spawn = child.spawn, | |||
|
|||
// Process arguments | |||
id = process.argv[2], | |||
done = process.argv[3], | |||
|
|||
// Localized application references | |||
user_repo = "", | |||
tracker = "", | |||
|
|||
// Initialize config file | |||
config = new Function( "return " + fs.readFileSync( __dirname + "/config.js" ) )(); | |||
|
|||
process.stdout.write( "Initializing... " ); | |||
|
|||
// If the user or token is blank, check git config and fill them in from there | |||
if ( !config.gitconfig.user || !config.gitconfig.token ) { | |||
exec( "git config --get-regexp github", function( error, stdout, stderr ) { | |||
config.gitconfig.user = config.gitconfig.user || (/github.user (.*)/.exec( stdout ) || [])[1]; | |||
config.gitconfig.token = config.gitconfig.token || (/github.token (.*)/.exec( stdout ) || [])[1]; | |||
|
|||
init(); | |||
}); | |||
|
|||
} else { | |||
init(); | |||
} | |||
|
|||
function init() { | |||
if ( !id ) { | |||
exit( "No pull request ID specified, please provide one." ); | |||
} | |||
|
|||
// If user and token are good, run init. Otherwise exit with a message | |||
if ( config.gitconfig.user && config.gitconfig.token ) { | |||
exec( "git remote -v show upstream", function( error, stdout, stderr ) { | |||
user_repo = (/URL:.*?(\w+\/\w+)/.exec( stdout ) || [])[1]; | |||
tracker = config.repos[ user_repo ]; | |||
|
|||
if ( user_repo ) { | |||
tracker = tracker || "https://github.com/" + user_repo + "/issues/"; | |||
|
|||
getStatus(); | |||
|
|||
} else { | |||
exit( "External repository not found." ); | |||
} | |||
}); | |||
|
|||
} else { | |||
exit( "Please specify a Github username and token:\n http://help.github.com/git-email-settings/" ); | |||
} | |||
} | |||
|
|||
function getStatus() { | |||
|
|||
exec( "git status", function( error, stdout, stderr ) { | |||
if ( /Changes to be committed/i.test( stdout ) ) { | |||
if ( done ) { | |||
getPullData(); | |||
|
|||
} else { | |||
exit( "Please commit changed files before attemping a pull/merge." ); | |||
} | |||
|
|||
} else if ( /Changes not staged for commit/i.test( stdout ) ) { | |||
if ( done ) { | |||
exit( "Please add files that you wish to commit." ); | |||
|
|||
} else { | |||
exit( "Please stash files before attempting a pull/merge." ); | |||
} | |||
} else { | |||
if ( done ) { | |||
exit( "It looks like you've broken your merge attempt." ); | |||
|
|||
} else { | |||
getPullData(); | |||
} | |||
} | |||
}); | |||
} | |||
|
|||
|
|||
function getPullData() { | |||
process.stdout.write( "done.\n" ); | |||
process.stdout.write( "Getting pull request details... " ); | |||
|
|||
http.request({ | |||
host: "github.com", | |||
port: 443, | |||
path: "/api/v2/json/pulls/" + user_repo + "/" + id | |||
}, function (res) { | |||
var data = []; | |||
|
|||
res.on( "data", function( chunk ) { | |||
data.push( chunk ); | |||
}); | |||
|
|||
res.on( "end", function() { | |||
try { | |||
var pull = JSON.parse( data.join("") ).pull; | |||
|
|||
process.stdout.write( "done.\n" ); | |||
|
|||
if ( done ) { | |||
commit( pull ); | |||
|
|||
} else { | |||
mergePull( pull ); | |||
} | |||
|
|||
} catch( e ) { | |||
exit( "Error retrieving pull request from Github." ); | |||
} | |||
}); | |||
}).end(); | |||
} | |||
|
|||
function mergePull( pull ) { | |||
process.stdout.write( "Pulling and merging results... " ); | |||
var repo = pull.head.repository.url + ".git", | |||
repo_branch = pull.head.ref, | |||
branch = "pull-" + id, | |||
checkout = "git checkout -b " + branch; | |||
|
|||
exec( "git checkout master && git pull --rebase upstream master && " + checkout, function( error, stdout, stderr ) { | |||
if ( /fatal/i.test( stderr ) ) { | |||
exec( "git branch -D " + branch + " && " + checkout, doPull ); | |||
|
|||
} else { | |||
doPull(); | |||
} | |||
}); | |||
|
|||
function doPull( error, stdout, stderr ) { | |||
var pull_cmds = [ | |||
"git pull " + repo + " " + repo_branch, | |||
"git checkout master", | |||
"git merge --no-commit --squash " + branch | |||
]; | |||
getHEAD(function(){}); | |||
exec( pull_cmds.join( " && " ), function( error, stdout, stderr ) { | |||
if ( /Merge conflict/i.test( stdout ) ) { | |||
exit( "Merge conflict. Please resolve then run: " + | |||
process.argv.join( " " ) + " done" ); | |||
|
|||
} else { | |||
getHEAD(function(){}); | |||
process.stdout.write( "done.\n" ); | |||
commit( pull ); | |||
} | |||
}); | |||
} | |||
} | |||
|
|||
function commit( pull ) { | |||
process.stdout.write( "Getting author and committing changes... " ); | |||
|
|||
http.request({ | |||
host: "github.com", | |||
port: 443, | |||
path: "/" + user_repo + "/pull/" + id + ".patch" | |||
}, function( res ) { | |||
var data = []; | |||
|
|||
res.on( "data", function( chunk ) { | |||
data.push( chunk ); | |||
}); | |||
|
|||
res.on( "end", function() { | |||
var author = (/From: (.*)/.exec( data.join("") ) || [])[1], | |||
tmp = {}, urls = [], msg = "", | |||
search = pull.title + " " + pull.body, | |||
findBug = /#(\d{4,5})/g, | |||
match; | |||
|
|||
while ( (match = findBug.exec( search )) ) { | |||
tmp[ match[1] ] = 1; | |||
} | |||
|
|||
msg = "Landing pull request " + id + ". " + pull.title + " Fixes "; | |||
|
|||
urls.push( "https://github.com/" + user_repo + "/pull/" + id ); | |||
|
|||
msg += (Object.keys( tmp ).sort().map(function( num ) { | |||
if ( tracker ) { | |||
urls.push( tracker + num ); | |||
} | |||
|
|||
return "#" + num; | |||
}).join(", ") || "#????") + "."; | |||
|
|||
msg += "\n\nMore Details:" + urls.map(function( url ) { | |||
return "\n - " + url; | |||
}).join(""); | |||
|
|||
var commit = [ "commit", "-a", "-e", "--message=" + msg ]; | |||
|
|||
if ( author ) { | |||
commit.push( "--author=" + author ); | |||
} | |||
|
|||
getHEAD(function( oldCommit ) { | |||
// Thanks to: https://gist.github.com/927052 | |||
process.stdout.write( "\n\noldCommit: " + oldCommit + "\ncommit: " + commit + "\n\n"); | |||
spawn( "git", commit, { customFds: | |||
[ process.stdin, process.stdout, process.stderr ] } ) | |||
.on("exit", function() { | |||
getHEAD(function( newCommit ) { | |||
process.stdout.write( "\nnewCommit: " + newCommit ); | |||
if ( oldCommit === newCommit ) { | |||
reset( "No commit, aborting push." ); | |||
|
|||
} else { | |||
exec( "git push upstream master", function( error, stdout, stderr ) { | |||
process.stdout.write( "done.\n" ); | |||
closePull( newCommit ); | |||
}); | |||
} | |||
}); | |||
}); | |||
}); | |||
}); | |||
}).end(); | |||
} | |||
|
|||
function closePull( commit ) { | |||
process.stdout.write( "Commenting on and closing pull request... " ); | |||
|
|||
var auth = "login=" + config.gitconfig.user + "&token=" + config.gitconfig.token; | |||
|
|||
http.request({ | |||
host: "github.com", | |||
port: 443, | |||
path: "/api/v2/json/issues/comment/" + user_repo + "/" + id, | |||
method: "POST", | |||
headers: { "Content-Type": "application/x-www-form-urlencoded" } | |||
}, function (res) { | |||
http.request({ | |||
host: "github.com", | |||
port: 443, | |||
path: "/api/v2/json/issues/close/" + user_repo + "/" + id, | |||
method: "POST", | |||
headers: { "Content-Type": "application/x-www-form-urlencoded" } | |||
}, function() { | |||
process.stdout.write( "done.\n" ); | |||
}).end( auth ); | |||
}).end( auth + "&comment=Landed in commit " + commit + "." ); | |||
} | |||
|
|||
function getHEAD( fn ) { | |||
exec( "git log | head -1", function( error, stdout, stderr ) { | |||
var commit = (/commit (.*)/.exec( stdout ) || [])[1]; | |||
process.stdout.write( "\ncommit: " + commit ); | |||
fn( commit ); | |||
}); | |||
} | |||
|
|||
function reset( msg ) { | |||
console.error( "\n" + msg ); | |||
process.stderr.write( "Resetting files... " ); | |||
|
|||
exec( "git reset --hard ORIG_HEAD", function() { | |||
process.stderr.write( "done.\n" ); | |||
exit(); | |||
}); | |||
} | |||
|
|||
function exit( msg ) { | |||
if ( msg ) { | |||
console.error( "\nError: " + msg ); | |||
} | |||
|
|||
process.exit( 1 ); | |||
} |