I Prefer Jim Developer James Schubert shares his code and his thoughts.

7Apr/13Off

git prepare-commit-msg with node.js

I think people often overlook using node.js as a scripting tool like ruby or python. If you search the web for examples of customized git hooks, you'll probably have visited git-scm.com, where most of the examples are in ruby.

I wanted to show a pretty simple prepare-commit-msg hook written in node.js. This hook will append the name and description of whatever working branch you commit from. I use this technique to create a working branch for a JIRA ticket number, so all my commits to this ticket automatically include that ticket number. Then, JIRA can be configured to display github commits directly in the ticket details when that commit references the ticket in the commit message.

The script:

#!/usr/bin/env node
var exec = require('child_process').exec,
    util = require('util'),
    fs = require('fs'),
    contents = null,
    branch, desc;

console.log(process.argv);

// expect .git/COMMIT_EDITMSG
if(/COMMIT_EDITMSG/g.test(process.argv[2])){
    // look for current branch name
    branch = exec("git branch | grep '*'",
      function (err, stdout, stderr) {
        if(err){
			// git branch will fail if initial commit has not been done,
			// so we can safely skip this hook
			process.exit(0); 
		}

		// opens .git/COMMIT_EDITMSG
        contents = fs.readFileSync(process.argv[2]);
		
		// trims extra characters from start/end of line
        var name = stdout.replace('* ','').replace('\n','');
		
		// If the branch has a description, pull that
        desc = exec('git config branch.'+ name +'.description',
            function(err, stdout, stderr){
				// don't handle errors (because we write out branch anyway)
                
				// we found a description, add to 'name'
				if(stdout){ name = util.format('%s (%s)', name, stdout.replace(/\n/g,'')); }

				// '(no branch)' indicates we are in a rebase or other non-HEAD scenario
                if(name !== '(no branch)'){
				
					// Append branch name to original contents.
                    contents = util.format('%s\n\n:%s\n', contents, name);
					
					// write contents back out to .git/COMMIT_EDITMSG
                    fs.writeFileSync(process.argv[2], contents);
                    process.exit(0);
                } else {
                    process.exit(0);
                }
        });
    });
}

The above script is pretty well-commented. If you decide to use it, you may want to comment out the first console.log which dumps all arguments sent to the script.

Here's an example of its usage.

Create and initialize a new local git repository.

$ mkdir example
$ cd example
$ git init

Then, copy the above example script into the git hooks directory under .git/hooks/prepare-commit-msg

Now, in the example directory, create and commit a file into the repository:

$ touch first
$ git add .
$ git commit -m 'Initial commit'

You'll see the array of arguments passed through the custom git hook, but if you issue a git log, you'll only see the commit message 'Initial commit'. That's because this custom hook expects a git branch, but there is no git branch until your initial commit is complete. To verify the script is working:

$ touch second
$ git add .
$ git commit -m 'Second commit'

A git log will now show you a tweaked message:

    Second commit
    
    :master

Now, you can create a branch named 'ABC-123' and do another commit:

$ git checkout -b ABC-123
$ touch third
$ git add .
$ git commit -m 'Third commit'

The commit message:

    Third commit
    
    :ABC-123

This hook also allows you to print out the branch description. To test, invoke the branch description editor:

$ git branch --edit-description ABC-123

I've added the text 'Supporting branch for ABC-234 and ABC-567'. Save this buffer according to your editor (:wq if you're using vim).

Add a fourth file as we have before, then verify with git log:

    Fourth commit
    
    :ABC-123 (Supporting branch for ABC-234 and ABC-567)

Because this script strips newlines from the branch description, you'd need to keep it short. Usually, that's not a problem.

This shows how to append text to your commit message, but it can easily be modified to replace commonly misspelled words (teh -> the).

This prepare-commit-msg hook is available in my blogs repo on github.

flattr this!

Tagged as: , No Comments
20Feb/12Off

git push: fatal: unable to read SHA1

Today, I was faced with an interesting error in a git repository. I am backing up a lot of old projects from during and after college into a private git repo. In doing so, I moved some folders around which disconnected a couple of binary files. After pushing, I received an error: unable to read [SHA1].

The fixes, in short:

$ git fsck
$ git log --raw --all --full-history | grep SHA1-HERE
$ git hash-object -w OBJECT-PATH-HERE
$ git push

Here is the error and a walk-through of coming up with the fixes above:

jim at schubert in /media/16GB/projects/school on master
$ git push
Password: 
Counting objects: 1945, done.
error: unable to find 2978ec4d75abb8c6bab225d8adfbd2bef064338a
error: unable to unpack bddbd13afd698e5ba7d572c9270e52bcac862661 header
error: inflateEnd: failed
Delta compression using up to 2 threads.
Compressing objects: 100% (1854/1854), done.
fatal: unable to read 2978ec4d75abb8c6bab225d8adfbd2bef064338a
fatal: The remote end hung up unexpectedly
fatal: The remote end hung up unexpectedly
fatal: write error: Bad file descriptor

After running git fsck, I found that I had two missing blobs:

jim at schubert in /media/16GB/projects/school on master*
$ git fsck 
dangling tree dbe9172996edbb7df517b0305c38891d78b72f66
dangling tree fbf7d8336b5f2347da23eb8a3938de5ab18f783c
missing blob 2978ec4d75abb8c6bab225d8adfbd2bef064338a
missing blob bddbd13afd698e5ba7d572c9270e52bcac862661

To fix this, I had to get the filenames of these blobs and write them back into the repository:

jim at schubert in /media/16GB/projects/school on master*
$ git log --raw --all --full-history | grep bddbd13
:000000 100644 0000000... bddbd13... A	INFO 465/Project2/UseCase/Diagrams/Leader - Time & Mileage.vsd

jim at schubert in /media/16GB/projects/school on master*
$ git log --raw --all --full-history | grep 2978ec4
:000000 100644 0000000... 2978ec4... A	INFO 465/Project2/Prototype/WebPrototype/WebPrototype/bin/WebPrototype.dll

Writing these files back into the repository, the push was successful. To write these back, do the following:

jim at schubert in /media/16GB/projects/school on master
$ git hash-object -w INFO\ 465/Project2/UseCase/Diagrams/Leader\ -\ Time\ \&\ Mileage.vsd
bddbd13afd698e5ba7d572c9270e52bcac862661
jim at schubert in /media/16GB/projects/school on master
$ git hash-object -w INFO\ 465/Project2/Prototype/WebPrototype/WebPrototype/bin/WebPrototype.dll
2978ec4d75abb8c6bab225d8adfbd2bef064338a

flattr this!

Tagged as: , No Comments
6Feb/12Off

Mastering Node: Addons and FunctionTemplate (uuid.node)

Last night, I pushed an addition to my fork of Mastering Node. I decided to add a bit to the Addons chapter. The first example in this chapter only shows how to add a function to a natively-compiled module (i.e. an addon). This example shows you how to start a module which can be used in the following way:

var Uuid = require('./uuid.node').Uuid;
var uuid = new Uuid();
var myId = uuid.generate();

The project files referenced in the following text can be downloaded from the repo: jimschubert/masteringnode


FunctionTemplate

In v8, a FunctionTemplate is used to create the equivalent to:

var template = function() { }

The function at this point is an object and not an instance of the function.

As an example, we will use the linux package uuid to generate a uuid. We will define the header for this addon as:

flattr this!

3Feb/12Off

words.pl: slogan word generator

About a year ago, I was really into playing this game online where you were given a single sentence and you had to use the letters in that sentence to make up as many words as possible. The longer the word, the higher the points.

Creating a script may be considered cheating if you're in it for money. If you're in it for fun, script away. That's what I always say.

Here's the gist of it:

#!/usr/bin/env perl
# words.pl: Find all possible slogan words from a single sentence. 
use strict; $|++;

@ARGV == 2 or die "usage: $0 input_file output_file 'sentence'\n";
my ($infile, $outfile, $sentence) = @ARGV;
$sentence = $sentence || 'how much wood could a woodchuck chuck';

open INPUT, "< $infile" or die $!;
open OUTPUT, "> $outfile" or die $!;

my $stdout = select STDOUT;
$| = 1;
select $stdout;

my %sentence_letters;
my $stmp = $sentence;
$sentence_letters{$&}++ while($stmp =~ s/[a-z]//);

print "Using the sentence '$sentence'\n";
print "Found the following letters:\n";
print "\t$_ - ". $sentence_letters{$_} ."\n" foreach(sort(keys %sentence_letters));
print "Processing $infile for slogan words\n";

my $count = 0;
my @indicators = qw{\ / | .};
LINE: while(<INPUT>) {
    my $word = $_;
    my $tmp = $word;
    next LINE if($word =~ /['\&\d]/);
    my %word_letters;
    $word_letters{$&}++ while($tmp =~ s/[a-z]//);
    
    foreach(keys %word_letters) {
        next LINE if ($word_letters{$_} > $sentence_letters{$_});
    }
    print OUTPUT $word;

    my $word_len = length($word);
    open WORD_LEN_OUTPUT, ">> $outfile.$word_len";
    print WORD_LEN_OUTPUT $word;

    print $indicators[++$count % 4], "\r";
}

print "\nDone.\nView $outfile.* for words\n";

When I wrote this, I had only recently started using Perl. Please go easy on me if it's poorly written.

The script takes an input file, an output file format (e.g. words.txt will be words.txt.20 for words of 20 characters), and an optional sentence to parse.

It gets a set of letters in the sentence, then runs through the list of words to see if the word can be made from any combination of letters.

For instance, if your 'sentence' is "baby cakes", the script will create a hash of those letters and their counts. Conceptually, this looks like:

// hash is an array
hash['a'] = 2
hash['b'] = 2
hash['c'] = 1
hash['e'] = 1
hash['k'] = 1
hash['s'] = 1
hash['y'] = 1

If, while walking line-by-line through your list of words, the script sees 'abracadabra', the loop will return false because (conceptually):

word['a'] = 5
word['a'] <= hash['a'] == false

The script also employs some interesting stdout manipulation. This allows the script to output "spinner text" and update the current line when the terminating character is a line-feed.

To run the script in a linux-based environment, you may do:

mkdir ~/projects && cd ~/projects
git clone git://gist.github.com/1733871.git gist-1733871
cd gist-gist-1733871
perl words.pl /usr/share/dict/words generated.txt 'Good goly, Miss Molly'

You should see output similar to:


jim at schubert in ~/projects/gist-1733871 on master*
$ tree .
.
├── generated.txt
├── generated.txt.1
├── generated.txt.2
├── generated.txt.3
├── generated.txt.4
├── generated.txt.5
├── generated.txt.6
├── generated.txt.7
├── generated.txt.8
└── words.pl

0 directories, 10 files

If you look at generated.txt.7, you will probably see something similar to:

Hollis
Osgood
glossy
goodly
idylls
igloos
solids

flattr this!

Tagged as: , No Comments
18Jan/12Off

dotfiles backup using GitHub

I was recently looking for a solution to backup my configuration files (bash, vim, etc) using GitHub. After some looking around, I've compiled a pretty nice project for myself.

github:jimschubert/dotfiles

First, this script checks dependencies. My dependencies are git, ruby, vim, tree, rake, gem, bundle, and trash. You could check out the code and add any number of dependencies here. Rubygems and bundler are required because the script later installs all gems listed in Gemfile.

Next, the script copies ~/.bashrc to ~/.bashrc.local. This allows you to keep your current bash configuration as a 'local-only' config that doesn't get copied or committed to github.

The script, as I copied most of bootstrap.sh and the rakefile from @gf3, expects the repository to be cloned to ~/.dotfiles. From there, it calls rake.

Rake looks at every file in ~/.dotfiles and copies the corresponding file relatively from ~/ to, essentially, ~/dotfiles-backup/`date`. I recommend first running the backup to make sure your files are properly backed up.

rake backup

The script then calls 'bundle install' to install all gems. It then copies all files from ~/.dotfiles to replace those relative files that were previously backed up from ~/.

The post-install displays a message to remind you to edit .gitconfig and .hgrc.

Because I've done some copying and compiling, these are relative close to the three projects in the README for right now.

Here is an excerpt from the README:

Bash

$ tree ~/.bash
/home/jim/.bash
├── aliases
├── completions
├── completion_scripts
│   └── git_completion
├── config
├── functions
├── paths
└── prompt

The above files are loaded by .bashrc. The files are pretty self-explanatory, other than prompt which colorizes the bash prompt with tweaks for git.

Cool Aliases

  • cd : pushd
  • bd : popd
  • cd.. | .. : back one directory
  • cd... | ... : back two directories
  • ^ up to five directories
  • rm : trash
  • undopush
  • ip
  • GET | HEAD | POST | PUT | DELETE | TRACE | OPTIONS

Config

  • sets editor to vim
  • sets English/UTF-8
  • sets manpager
  • sets commands to ignore in history
  • sets noclobber (e.g. prevents cat > IMPORTANT_FILE mistakes )
  • sets nocaseglob (e.g. ls ~/.B* will list contents of ~/.bash)

Functions

The two functions, md and c may not seem like much, but they simplify some commands. For example:

$ md projects; git clone git@github.com:jimschubert/dotfiles.git && cd dotfiles

In the above line, md will create the projects directory and cd into it.

c stands for 'code' and works like this:

jim at computer in ~
$ pwd
/home/jim
jim at computer in ~
$ c dotfiles
~/projects/dotfiles ~
jim at computer in ~/projects/dotfiles on master
$

You can change it to whatever shortcut and issue reload, which is also an alias from this setup.

Screenshot

Notice the color scheme and github branch notifications created by ~/.bash/prompt.

flattr this!

31Oct/11Off

jQuery plugin: fixed table header

Here's a plugin that I wrote a while ago for fixing a table's header row on scroll.

This code is also available as a gist. Fork it and contribute.

(function($) { 
	$.fn.fixedHeader = function(options) {
		var settings = { 
			selector: 'thead:first',
			cssClass: 'fixed',
			fixTo: 0
		};

		var _fixHeader = function(obj) { 
			var header = $(obj.selector, obj.elem);
			if(header) {
				var parent =  header.parents('table:first') || header.parent();
				(parent && parent.css({ borderCollapse: 'collapse'}) );

				var data = header.data('fixedHeader') || header.data('fixedHeader', { 
						top: header.offset().top,
						width: parent.find('tr:eq(1)').width(),
						cells: parent.find('tr:eq(1) > td'),
						processed: false
					});
				var top = data.top - $(document).scrollTop();
				if( top < 0 ) {
					header.addClass(obj.css);	
					if(!data.processed){
						header.width(data.width);
						for(var i = 0; i<data.cells.length;i++) {
							$('th:eq('+i+')', header).width($(data.cells[i]).width());
						}
					}

				} else { 
					header.removeClass(obj.css); 
				}
			}		
		};
		return this.each(function() {
			var self = this;
			if("object" === typeof options) {
				$.extend(settings, options);
			}
			if($(self).parents('table:first')){
				$(window).bind('scroll.fixedHeader', function() {
					_fixHeader({ 
						elem: self,
						selector: settings.selector,
						css: settings.cssClass,
						top: settings.fixTo
					});
				});
			}
		});
	};
})(jQuery);

A demo:

flattr this!

29Oct/11Off

Mercurial and Git in one repository

I have a personal project hosted at bitbucket using Mercurial as the version control system. Since I started that project, I've been using GitHub for everything. I just found hg-git instructions on github.com. It is pretty awesome.

For my purposes, it allows me to maintain the project on two remote servers, one running Mercurial, the other running Git.

To see how it works, first install hg-git:

$ sudo easy_install hg-git

Then, edit your ~/.hgrc settings, adding to the [extensions] section:

[extensions]                                                                                                                                      
hgext.bookmarks =
hggit =

Also, be sure your ~/.hgrc contains a valid email address:

[ui]
username = Jim Schubert <james.schubert@gmail.com>

Now, you can create a repository on github.com and push your Mercurial commits:

$ cd ~/projects/project_name
$ hg bookmark -r default master
$ hg push git+ssh://git@github.com/username/project_name.git
$ hg push

If you're only planning on using Mercurial to push changes to github or some other Git host, you can add that path to ~/.hgrc:

[paths]
default-push = git+ssh://git@github.com/username/project_name.git

flattr this!

Tagged as: , No Comments
7Aug/11Off

jquery.empuzzle @github

For the past few days, I've been writing this plugin called jquery.empuzzle. It was inspired by 'Jigsy' at cityposh.com.

It is a basic N-puzzle

The syntax is as simple as calling the plugin on a single image.

    $('#second').empuzzle();

You can even get a little more into it and provide quite a few options.

$(function() {
   $('img').empuzzle({ 
        size: 4,
        target: $('#target'),
        blank: 'BR',
        randomize: function(game, defaultRandomizer) {
            defaultRandomizer.call(this, game);
        }, 
        win: function(game) { 
            alert("You're a winner!"); 
        },
        anim: { 
            duration: 200, 
            complete: function() { console.log('Move completed!'); }
        },
        DEBUG: true
   });
});

You could easily add a move counter in the anim.complete function. That function is basically the complete function that normally gets passed to jquery.animate(). I've had to curry the anim.complete function so the normal complete function performs the necessary internal tasks.

Enough about all that, check it out!

flattr this!

6Jun/11Off

Quake tracker is now officially open-source

Go here, fork it.

https://github.com/jimschubert/quaketracker

flattr this!

16Mar/11Off

Mastering Node

I've forked a project at github called "Mastering Node".
https://github.com/jimschubert/masteringnode

I've wanted to familiarize myself with node.js for some time, and this seems to be the best way. It's an amazing framework for server-side JavaScript.

flattr this!