Permalink
Browse files

Pulley for upstream jQuery; this is for my own benefit

  • Loading branch information...
0 parents commit ab11a8c08ced167ee07be61493d76fab95a86777 timmywil committed Apr 28, 2011
Showing with 360 additions and 0 deletions.
  1. +19 −0 LICENSE
  2. +21 −0 README.md
  3. +13 −0 config.js
  4. +17 −0 package.json
  5. +290 −0 pulley.js
19 LICENSE
@@ -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.
@@ -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
@@ -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: ""
+ }
+})
+
@@ -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 pulley.js
@@ -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.