Skip to content

Commit

Permalink
Pulley for upstream jQuery; this is for my own benefit
Browse files Browse the repository at this point in the history
  • Loading branch information
timmywil committed Apr 28, 2011
0 parents commit ab11a8c
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 0 deletions.
19 changes: 19 additions & 0 deletions LICENSE
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.
21 changes: 21 additions & 0 deletions README.md
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
13 changes: 13 additions & 0 deletions config.js
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: ""
}
})

17 changes: 17 additions & 0 deletions package.json
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"
}]
}
290 changes: 290 additions & 0 deletions pulley.js
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 );
}

0 comments on commit ab11a8c

Please sign in to comment.