<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jasper Tandy &#187; code</title>
	<atom:link href="http://jspr.tndy.me/category/code/feed/" rel="self" type="application/rss+xml" />
	<link>http://jspr.tndy.me</link>
	<description></description>
	<lastBuildDate>Fri, 03 Feb 2012 18:58:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>An Automator Folder Action for Screenshots and Dropbox</title>
		<link>http://jspr.tndy.me/an-automator-folder-action-for-screenshots-and-dropbox/</link>
		<comments>http://jspr.tndy.me/an-automator-folder-action-for-screenshots-and-dropbox/#comments</comments>
		<pubDate>Thu, 01 Sep 2011 08:39:33 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[applescript]]></category>
		<category><![CDATA[automator]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[workflow]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=3939</guid>
		<description><![CDATA[Photos of dogs and cats and leaves and horizontal lines will be back soon.]]></description>
			<content:encoded><![CDATA[<p>I strive for minimalism when using computers. Less software, less processes, less distraction. I have a block colour as my desktop background. I don&#8217;t install superfluous software, no matter how beautifully-crafted. My computer is a tool (well, my OSX partition is anyway), not a game, so I treat it as such. If you rely on your computer, treat it well. Know where everything is and know what&#8217;s installed at all times. If something becomes surplus to requirements, remove it.</p>
<p>I use <a href="https://www.dropbox.com">Dropbox</a> as my cloud sharing weapon of choice. I share folders with friends and colleagues, and all of my screenshots are saved to a sub-folder of my public folder so that I can easily share them with anyone. This workflow might seem inherently insecure, but I don&#8217;t tend to screenshot things that are particularly secret; whether that&#8217;s a result of my screenshots having been public for so long, I don&#8217;t know.</p>
<p>You can easily change your screenshots directory from your Desktop to your Dropbox folder by opening a Terminal (/Applications/Utilities/Terminal.app) and running the following command:<br />
<code id="screenshotpath"><br />
defaults write com.apple.screencapture location /Path/to/Dropbox/Public/Screenshots<br />
</code></p>
<p>I like to have them in their own folder as I use Public for other things and don&#8217;t want it becoming overrun with screenshots. I don&#8217;t delete images; occupational hazard.</p>
<p>You can now close your Terminal window and go back to ignoring it if it scares you. I know a lot of people are uncomfortable with it. Once you&#8217;re saving screenshots to that directory, you can <a href="/downloads/copy-screenshot-url.workflow.zip">download the Automator Workflow</a>. Unzip and open with Automator. You&#8217;ll be asked if you want to install it. If you trust me, click install and the workflow is copied to the correct directory and opened for you to edit (you&#8217;re going to need to edit).</p>
<p>You should be presented with a screen that looks a little something like this:</p>
<p><img src="http://jspr.tndy.me/up/2011/09/gljtupsuzartjbgyvqmf-826x900.png" alt="Automator Screenshot" /></p>
<p>We&#8217;re only interested in the first box, where you&#8217;ve got some config to edit.</p>
<p><code>dropbox</code>: The path to your Dropbox folder.<br />
<code>screenshots</code>: The path to your Screenshots folder. Must be relative to <code>$dropbox</code>.<br />
<code>dropboxuid</code>: Your Dropbox User ID. Go into your Dropbox Public folder, context-click (right click, thanks Apple) > Dropbox > Copy Public Link. Your User ID is the big number in the URL copied.<br />
<code>bitlyuser</code>: Your <a href="https://bitly.com/">bit.ly</a> username.<br />
<code>bitlyapikey</code>: Your bit.ly <a href="https://bitly.com/a/account">API Key</a> can be found at the bottom of <a href="https://bitly.com/a/account">this page</a>.</p>
<p>You may not care if your URL gets shortened. If this is the case; find the following block of code in the first box:<br />
<code><br />
short=`curl -m 4 -0 "http://api.bit.ly/v3/shorten?login=$bitlyuser&#038;apiKey=$bitlykey&#038;format=txt&#038;uri=$encoded"`<br />
if [ -z $short ]<br />
then<br />
&nbsp;&nbsp;&nbsp;&nbsp;short=$url<br />
fi<br />
echo $short<br />
</code></p>
<p>And replace it with:<br />
<code><br />
echo $url<br />
</code></p>
<p>Now you&#8217;ll just get the Dropbox Public URL copied to your clipboard; not bit.ly (which bit.ly will probably prefer if you take a lot of screenshots!)</p>
<p>I&#8217;m making the following assumptions about your system:</p>
<ul>
<li>You have <a href="http://growl.info/">Growl.app</a> installed. If you&#8217;re not sure, open System Preferences and look for a pane under &#8220;Other&#8221; called &#8220;Growl&#8221;.</li>
<li>You have <a href="https://www.dropbox.com">Dropbox</a> installed</li>
<li>You have changed your Screenshots path (using the <a href="#screenshotpath">method above</a>) to be within your Dropbox Public folder</li>
<li>You have OSX Lion installed. I don&#8217;t see why it shouldn&#8217;t work on Snow Leopard, but I haven&#8217;t tried and I can&#8217;t.</li>
</ul>
<p>If you satisfy those criteria, you should have success with this workflow. It is, however, offered with no support and I will accept no responsibility if you break your computer playing around in the Terminal because it looks like The Matrix or trying to update Ruby (system Ruby works fine) or something. If you&#8217;re friendly and would like some help, please ask on my <a href="http://www.formspring.me/jaspertandy">Formspring</a> and I&#8217;ll try to help. I&#8217;m by no means an expert at Automator, bash or zsh scripting or AppleScript but I can get things to work. I suck at AppleScript so badly that I had to paraphrase the AppleScript portion of this Workflow from <a href="http://growl.info/documentation/applescript-support.php">here</a>.</p>
<p>Yes, <a href="http://www.planetperki.co.uk/">Perkins</a>, I&#8217;ve heard of <a href="http://getcloudapp.com/">Cloud.app</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/an-automator-folder-action-for-screenshots-and-dropbox/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>yet more effort toward keeping config out of git</title>
		<link>http://jspr.tndy.me/yet-more-effort-toward-keeping-config-out-of-git/</link>
		<comments>http://jspr.tndy.me/yet-more-effort-toward-keeping-config-out-of-git/#comments</comments>
		<pubDate>Tue, 01 Mar 2011 17:56:29 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2460</guid>
		<description><![CDATA[It seems that I spend most of my waking life concerned about keeping usernames and passwords out of version control. It causes at least 117% of the merge/rebase conflicts that I come across every day, and I&#8217;m getting pretty tired of it, to tell you the truth. My latest genius idea involves the much-lauded pre-commit [...]]]></description>
			<content:encoded><![CDATA[<p>It seems that I spend most of my waking life concerned about keeping usernames and passwords out of version control. It causes at least 117% of the merge/rebase conflicts that I come across every day, and I&#8217;m getting pretty tired of it, to tell you the truth.</p>
<p>My latest genius idea involves the much-lauded pre-commit <a href="http://www.kernel.org/pub/software/scm/git/docs/githooks.html">hook in git</a>. To save your mouse-clickin&#8217; finger; the pre-commit hook is executed before git-commit does anything dangerous, and if you exit non-zero, it&#8217;ll stop the commit. That&#8217;s pretty useful in my book.</p>
<p>So, here&#8217;s the idea. You do some work involving something that needs a config file. You commit it <em>with blank config</em>. Lovely. Now, you add your configuration to the files that need it, but don&#8217;t commit. Instead, format a patch of the changes to your working copy. I use the script below:</p>
<div class="code">
<pre>
<span class="lnr">1 </span><span class="Comment">#!/bin/bash</span>
<span class="lnr">2 </span><span class="Conditional">if </span><span class="Operator">[</span> <span class="PreProc">$#</span> <span class="Conditional">-ne</span> <span class="Constant">1</span> <span class="Operator">]</span><span class="Operator">;</span> <span class="Conditional">then</span>
<span class="lnr">3 </span>    <span class="Statement">echo</span><span class="Constant"> </span><span class="Operator">&quot;</span><span class="Constant">Supply a patch name.</span><span class="Operator">&quot;</span>
<span class="lnr">4 </span>    <span class="Statement">exit</span>
<span class="lnr">5 </span><span class="Conditional">fi</span>
<span class="lnr">6 </span>git diff <span class="Special">--no-prefix</span> <span class="Special">--no-ext-diff</span> <span class="Operator">&gt;</span> <span class="PreProc">$@</span>
</pre>
</div>
<p>I save my patch to the following location: <sm>~/Patches/repo.directory.branchname.patch</em>, then install this pre-commit hook:</p>
<div class="code">
<pre>
<span class="lnr"> 1 </span><span class="PreProc">#!/usr/bin/env ruby</span>
<span class="lnr"> 2 </span>
<span class="lnr"> 3 </span>dir = <span class="Type">File</span>.expand_path( <span class="Type">File</span>.dirname(<span class="Delimiter">'</span><span class="Constant">.</span><span class="Delimiter">'</span>) )
<span class="lnr"> 4 </span>repo = dir.split(<span class="Delimiter">'</span><span class="Constant">/</span><span class="Delimiter">'</span>).last
<span class="lnr"> 5 </span>branch = <span class="Delimiter">`</span><span class="Constant">git branch | grep '^*'</span><span class="Delimiter">`</span>.to_s.gsub(<span class="Delimiter">/</span><span class="Special">^</span><span class="Special">\*</span><span class="Delimiter">/</span> , <span class="Delimiter">''</span>).strip
<span class="lnr"> 6 </span>patchname = <span class="Delimiter">&quot;</span><span class="Delimiter">#{</span>repo<span class="Delimiter">}</span><span class="Constant">.</span><span class="Delimiter">#{</span>branch<span class="Delimiter">}</span><span class="Constant">.patch</span><span class="Delimiter">&quot;</span>
<span class="lnr"> 7 </span>patchdir = <span class="Type">File</span>.expand_path( <span class="Delimiter">'</span><span class="Constant">~/Patches</span><span class="Delimiter">'</span> )
<span class="lnr"> 8 </span>patchpath = <span class="Delimiter">&quot;</span><span class="Delimiter">#{</span>patchdir<span class="Delimiter">}</span><span class="Constant">/</span><span class="Delimiter">#{</span>patchname<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr"> 9 </span>
<span class="lnr">10 </span><span class="Conditional">if</span> <span class="Type">File</span>.exists? patchpath
<span class="lnr">11 </span>    <span class="Comment"># try to dry-run removing the patch - if it spits shit out that doesn't</span>
<span class="lnr">12 </span>    <span class="Comment"># contain the word &quot;FAILED&quot;, means the patch was already removed so we</span>
<span class="lnr">13 </span>    <span class="Comment"># can continue</span>
<span class="lnr">14 </span>    <span class="Comment">#</span>
<span class="lnr">15 </span>    <span class="Comment"># if the patch runs silently or contains the word &quot;FAILED&quot; it means</span>
<span class="lnr">16 </span>    <span class="Comment"># something's up</span>
<span class="lnr">17 </span>    dry = <span class="Delimiter">`</span><span class="Constant">patch -s --no-backup --dry-run -p0 -R &lt; &quot;</span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Constant">&quot;</span><span class="Delimiter">`</span>.to_s.strip
<span class="lnr">18 </span>    <span class="Conditional">if</span> dry == <span class="Delimiter">''</span>
<span class="lnr">19 </span>        puts <span class="Delimiter">'</span><span class="Constant">There</span><span class="Special">\'</span><span class="Constant">s a patch that you should remove. Remove it then commit.</span><span class="Delimiter">'</span>
<span class="lnr">20 </span>        puts <span class="Delimiter">&quot;</span><span class="Constant">Patch File: </span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr">21 </span>        <span class="Statement">exit</span> <span class="Constant">1</span>
<span class="lnr">22 </span>    <span class="Conditional">elsif</span> dry.index( <span class="Delimiter">'</span><span class="Constant">FAILED</span><span class="Delimiter">'</span> ) != <span class="Constant">nil</span>
<span class="lnr">23 </span>        puts <span class="Delimiter">'</span><span class="Constant">A broken patch was found. Fix it before proceeding.</span><span class="Delimiter">'</span>
<span class="lnr">24 </span>        puts <span class="Delimiter">&quot;</span><span class="Constant">Patch File: </span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr">25 </span>        <span class="Statement">exit</span> <span class="Constant">1</span>
<span class="lnr">26 </span>    <span class="Conditional">end</span>
<span class="lnr">27 </span><span class="Conditional">end</span>
<span class="lnr">28 </span><span class="Statement">exit</span> <span class="Constant">0</span>
</pre>
</div>
<p>This basically dry-runs the removal of the appropriate patch. If the patch would exit without complaint, that means that the patch could be applied and what you&#8217;re committing contains configuration, so the commit fails. If the output of the dry-run contains the word &#8220;FAILED&#8221;, that means that the patch is invalid, so we err on the side of caution and fail. If the patch is noisy, but doesn&#8217;t contain the word fail, we can safely assume that the patch has already been removed and we&#8217;re OK to continue exiting.</p>
<p>I&#8217;m sure that next week, there&#8217;ll be a totally new, genius way to solve this problem. For now, however, this is working really well.</p>
<p>For ease, I have the following git commands, also:</p>
<p><strong>git-apatch</strong></p>
<div class="code">
<pre>
<span class="lnr"> 1 </span><span class="PreProc">#!/usr/bin/env ruby</span>
<span class="lnr"> 2 </span>
<span class="lnr"> 3 </span>dir = <span class="Type">File</span>.expand_path( <span class="Delimiter">`</span><span class="Constant">git rev-parse --show-toplevel</span><span class="Delimiter">`</span> )
<span class="lnr"> 4 </span>repo = dir.split(<span class="Delimiter">'</span><span class="Constant">/</span><span class="Delimiter">'</span>).last.strip
<span class="lnr"> 5 </span>branch = <span class="Delimiter">`</span><span class="Constant">git branch | grep '^*'</span><span class="Delimiter">`</span>.to_s.gsub(<span class="Delimiter">/</span><span class="Special">^</span><span class="Special">\*</span><span class="Delimiter">/</span> , <span class="Delimiter">''</span>).strip
<span class="lnr"> 6 </span>patchname = <span class="Delimiter">&quot;</span><span class="Delimiter">#{</span>repo<span class="Delimiter">}</span><span class="Constant">.</span><span class="Delimiter">#{</span>branch<span class="Delimiter">}</span><span class="Constant">.patch</span><span class="Delimiter">&quot;</span>
<span class="lnr"> 7 </span>patchdir = <span class="Type">File</span>.expand_path( <span class="Delimiter">'</span><span class="Constant">~/Patches</span><span class="Delimiter">'</span> )
<span class="lnr"> 8 </span>patchpath = <span class="Delimiter">&quot;</span><span class="Delimiter">#{</span>patchdir<span class="Delimiter">}</span><span class="Constant">/</span><span class="Delimiter">#{</span>patchname<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr"> 9 </span>
<span class="lnr">10 </span><span class="Conditional">if</span> <span class="Type">File</span>.exists? patchpath
<span class="lnr">11 </span>    puts <span class="Delimiter">`</span><span class="Constant">patch -p0 <  &quot;</span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Constant">&quot;</span><span class="Delimiter">`</span>
<span class="lnr">12 </span><span class="Conditional">else</span>
<span class="lnr">13 </span>    puts <span class="Delimiter">&quot;</span><span class="Constant">No patch in </span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr">14 </span><span class="Conditional">end</span>
</pre>
</div>
<p><strong>git-rmpatch</strong></p>
<div class="code">
<pre>
<span class="lnr"> 1 </span><span class="PreProc">#!/usr/bin/env ruby</span>
<span class="lnr"> 2 </span>
<span class="lnr"> 3 </span>dir = <span class="Type">File</span>.expand_path( <span class="Delimiter">`</span><span class="Constant">git rev-parse --show-toplevel</span><span class="Delimiter">`</span> )
<span class="lnr"> 4 </span>repo = dir.split(<span class="Delimiter">'</span><span class="Constant">/</span><span class="Delimiter">'</span>).last.strip
<span class="lnr"> 5 </span>branch = <span class="Delimiter">`</span><span class="Constant">git branch | grep '^*'</span><span class="Delimiter">`</span>.to_s.gsub(<span class="Delimiter">/</span><span class="Special">^</span><span class="Special">\*</span><span class="Delimiter">/</span> , <span class="Delimiter">''</span>).strip
<span class="lnr"> 6 </span>patchname = <span class="Delimiter">&quot;</span><span class="Delimiter">#{</span>repo<span class="Delimiter">}</span><span class="Constant">.</span><span class="Delimiter">#{</span>branch<span class="Delimiter">}</span><span class="Constant">.patch</span><span class="Delimiter">&quot;</span>
<span class="lnr"> 7 </span>patchdir = <span class="Type">File</span>.expand_path( <span class="Delimiter">'</span><span class="Constant">~/Patches</span><span class="Delimiter">'</span> )
<span class="lnr"> 8 </span>patchpath = <span class="Delimiter">&quot;</span><span class="Delimiter">#{</span>patchdir<span class="Delimiter">}</span><span class="Constant">/</span><span class="Delimiter">#{</span>patchname<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr"> 9 </span>
<span class="lnr">10 </span><span class="Conditional">if</span> <span class="Type">File</span>.exists? patchpath
<span class="lnr">11 </span>    puts <span class="Delimiter">`</span><span class="Constant">patch -p0 -R < &quot;</span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Constant">&quot;</span><span class="Delimiter">`</span>
<span class="lnr">12 </span><span class="Conditional">else</span>
<span class="lnr">13 </span>    puts <span class="Delimiter">&quot;</span><span class="Constant">No patch in </span><span class="Delimiter">#{</span>patchpath<span class="Delimiter">}</span><span class="Delimiter">&quot;</span>
<span class="lnr">14 </span><span class="Conditional">end</span>
</pre>
</div>
<p>All these do is look for a patch to be replied or moved for the current repo/branch and apply or remove it, failing noisily if the patch doesn&#8217;t exist. I realise they share some code that could be extracted, but for the sake of easy display, this is what the scripts basically contain.</p>
<p>The only thing I think that could make this more useful would be to detect which files the patch would modify, apply the patch and unstage those files to allow the commit to proceed, thus causing fewer interruptions. That&#8217;s a nice-to-have, though. Absent-minded as I am, this has already saved me from committing config twice today!</p>
<p>If you&#8217;ve got a different approach that works well for you, I&#8217;d love to hear about it on the <a href="http://twitter.com/jaspertandy">twitters</a></p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/yet-more-effort-toward-keeping-config-out-of-git/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>bit tumblr-y, but a quote</title>
		<link>http://jspr.tndy.me/bit-tumblr-y-but-a-quote/</link>
		<comments>http://jspr.tndy.me/bit-tumblr-y-but-a-quote/#comments</comments>
		<pubDate>Fri, 22 Oct 2010 20:38:32 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[quote]]></category>
		<category><![CDATA[sense]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/2010/10/bit-tumblr-y-but-a-quote/</guid>
		<description><![CDATA[Non-developers often assume an application is invariably more valuable with a feature than without it Via literate programmer I can really empathise with this sentiment. There&#8217;s a huge temptation instilled somehow in people buying websites that more features = better quality. We really try to get clients to spend money on quality marketing at Buffalo, [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>Non-developers often assume an application is invariably more valuable with a feature than without it</p></blockquote>
<p>Via <a href="http://literateprogrammer.blogspot.com/2010/10/tests-are-facts-code-is-theory.html">literate programmer</a></p>
<p>I can really empathise with this sentiment. There&#8217;s a huge temptation instilled somehow in people buying websites that more features = better quality. We really try to get clients to spend money on quality marketing at <a href="http://www.builtbybuffalo.com">Buffalo</a>, but they rarely realise that a bucket of features is pretty irrelevant if people don&#8217;t know your site exists. In the above, you can substitute the word &#8220;site&#8221; and its variants above with anything really.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/bit-tumblr-y-but-a-quote/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Store git activity in MySQL with PHP</title>
		<link>http://jspr.tndy.me/store-git-activity-in-mysql-with-php/</link>
		<comments>http://jspr.tndy.me/store-git-activity-in-mysql-with-php/#comments</comments>
		<pubDate>Mon, 26 Jul 2010 16:07:39 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[gtd]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2183</guid>
		<description><![CDATA[Git hooks are saving me so much time and providing me with interesting solutions to problems I didn&#8217;t even know I had. I can&#8217;t be the only person who this would be useful to, so give it a go. As I said, I work on loads of sites, and keeping track of what&#8217;s been done [...]]]></description>
			<content:encoded><![CDATA[<p>Git hooks are saving me so much time and providing me with interesting solutions to problems I didn&#8217;t even know I had. I can&#8217;t be the only person who this would be useful to, so give it a go.</p>
<p><a href="http://jspr.tndy.me/2010/07/managing-multiple-working-copies-with-git/">As I said</a>, I work on loads of sites, and keeping track of what&#8217;s been done and where is sometimes a bit of a pain. I keep a <a href="http://culturedcode.com/things/">todo list</a>, but if I get an emergency email from someone, chances are that won&#8217;t go through my todos. It will, however, be put into version control.</p>
<p>So this morning I had the bright idea to write a git hook that pushes relevant information to MySQL so that I can run activity reports later. All my bare git repositories are stored in a directory on our <a href="http://www.bytemark.co.uk/index">dedi</a>, so it&#8217;s just a matter of making sure each repository has the post-receive hook in. I do this by keeping the actual hook in the same directory as all my repositories, then symlinking the hook into the appropriate place with the <a href="http://pastebin.com/QpzYsw93">following little script</a>. Obviously, this assumes that your post-receive hook is in the same place as your repositories, and that you want this hook everywhere. But that&#8217;s all true, so we&#8217;re all good. Once you&#8217;ve run the linked script, you&#8217;ll only have one hook to maintain and every time you create a new repository, you can just run the script again and everything will all be up-to-date.</p>
<p>Now for the hook. It&#8217;s not beautiful PHP, but little scripts like this rarely are, in my experience.</p>
<p>Create this table:</p>
<div class="code"><code><span class="Statement">CREATE</span>&nbsp;<span class="Statement">TABLE</span>&nbsp;`log` (<br />
`id` <span class="Type">int</span>(<span class="Number">10</span>) <span class="Statement">unsigned</span>&nbsp;<span class="Statement">NOT</span>&nbsp;<span class="Special">NULL</span>&nbsp;<span class="Statement">auto_increment</span>,<br />
`repo` <span class="Type">varchar(</span><span class="Number">255</span><span class="Type">)</span>&nbsp;<span class="Statement">NOT</span>&nbsp;<span class="Special">NULL</span>,<br />
`commit` <span class="Type">varchar(</span><span class="Number">40</span><span class="Type">)</span>&nbsp;<span class="Statement">NOT</span>&nbsp;<span class="Special">NULL</span>,<br />
`<span class="Type">date</span>` <span class="Type">datetime</span>&nbsp;<span class="Statement">NOT</span>&nbsp;<span class="Special">NULL</span>,<br />
`message` <span class="Type">text</span>&nbsp;<span class="Statement">NOT</span>&nbsp;<span class="Special">NULL</span>,<br />
<span class="Statement">PRIMARY</span>&nbsp;<span class="Statement">KEY</span>&nbsp;(`id`),<br />
<span class="Statement">UNIQUE</span>&nbsp;<span class="Statement">KEY</span>&nbsp;`commit` (`commit`)<br />
) ENGINE=<span class="Statement">MyISAM</span>&nbsp;<span class="Statement">DEFAULT</span>&nbsp;CHARSET=utf8;<br />
</code></div>
<p>Here&#8217;s your script. chmod +x it.</p>
<div class="code"><code><span class="Comment">#!/usr/bin/env php</span><br />
<span class="Delimiter">&lt;?php</span><br />
date_default_timezone_set<span class="Delimiter">(</span>'<span class="String">Europe/London</span>'<span class="Delimiter">)</span>;<br />
<span class="Function">exec</span><span class="Delimiter">(</span>'<span class="String">pwd</span>',<span class="Operator">$</span><span class="Identifier">pwd</span><span class="Delimiter">)</span>;<br />
<span class="Operator">$</span><span class="Identifier">repo</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Function">rtrim</span><span class="Delimiter">(</span><span class="Function">array_shift</span><span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">pwd</span><span class="Delimiter">)</span>,'<span class="String">/</span>'<span class="Delimiter">)</span>;<br />
<span class="Operator">$</span><span class="Identifier">repo</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Function">substr</span><span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">repo</span>,<span class="Function">strrpos</span><span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">repo</span>,'<span class="String">/</span>'<span class="Delimiter">)</span>&nbsp;<span class="Operator">+</span>&nbsp;<span class="Number">1</span><span class="Delimiter">)</span>;<br />
<span class="Operator">$</span><span class="Identifier">db</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="PreProc">new</span>&nbsp;<span class="Function">PDO</span><span class="Delimiter">(</span>'<span class="String">mysql:dbname=DB;host=127.0.0.1</span>','<span class="String">USERNAME</span>','<span class="String">PASSWORD</span>'<span class="Delimiter">)</span>;<br />
<span class="Function">exec</span><span class="Delimiter">(</span>'<span class="String">git log --all --pretty=format:&quot;%H%n%ct%n%s%n%b%n&lt;&gt;&lt;&gt;&lt;&gt;&quot;</span>',<span class="Operator">$</span><span class="Identifier">capture</span>,<span class="Operator">$</span><span class="Identifier">log</span><span class="Delimiter">)</span>;<br />
<span class="Conditional">if</span>&nbsp;<span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">capture</span><span class="Delimiter">){</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Comment">// preprocess the log</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">commits</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Type">array</span><span class="Delimiter">()</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">current</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Type">array</span><span class="Delimiter">()</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Statement">foreach</span>&nbsp;<span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">capture</span>&nbsp;<span class="Statement">as</span>&nbsp;<span class="Operator">$</span><span class="Identifier">row</span><span class="Delimiter">){</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Conditional">if</span>&nbsp;<span class="Delimiter">(</span><span class="Function">trim</span><span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">row</span><span class="Delimiter">)</span>&nbsp;<span class="Statement">===</span>&nbsp;'<span class="String">&lt;&gt;&lt;&gt;&lt;&gt;</span>'<span class="Delimiter">)</span>&nbsp;<span class="Delimiter">{</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">commits</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">current</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">current</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Type">array</span><span class="Delimiter">()</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span>&nbsp;<span class="Conditional">else</span>&nbsp;<span class="Delimiter">{</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">current</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">row</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">v</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Type">array</span><span class="Delimiter">()</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">b</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Type">array</span><span class="Delimiter">()</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Statement">foreach</span>&nbsp;<span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">commits</span>&nbsp;<span class="Statement">as</span>&nbsp;<span class="Operator">$</span><span class="Identifier">commit</span><span class="Delimiter">){</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">sha</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">commit</span><span class="Delimiter">[</span><span class="Number">0</span><span class="Delimiter">]</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">m</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">commit</span><span class="Delimiter">[</span><span class="Number">2</span><span class="Delimiter">]</span>&nbsp;<span class="Operator">.</span>&nbsp;<span class="Delimiter">(</span><span class="Function">trim</span><span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">commit</span><span class="Delimiter">[</span><span class="Number">3</span><span class="Delimiter">])</span>&nbsp;<span class="Statement">===</span>&nbsp;''&nbsp;<span class="Operator">?</span>&nbsp;''&nbsp;<span class="Operator">:</span>&nbsp;&quot;<span class="Special">\n\n</span>&quot;&nbsp;<span class="Operator">.</span>&nbsp;<span class="Function">implode</span><span class="Delimiter">(</span>&quot;<span class="Special">\n</span>&quot;,<span class="Function">array_slice</span><span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">commit</span>,<span class="Number">3</span><span class="Delimiter">)))</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">d</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Function">date</span><span class="Delimiter">(</span>'<span class="String">Y-m-d H:i:s</span>',<span class="Operator">$</span><span class="Identifier">commit</span><span class="Delimiter">[</span><span class="Number">1</span><span class="Delimiter">])</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">v</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;'<span class="String">(?,?,?,?)</span>';<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">b</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">repo</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">b</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">sha</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">b</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">m</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">b</span><span class="Delimiter">[]</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">d</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Operator">$</span><span class="Identifier">stmt</span>&nbsp;<span class="Operator">=</span>&nbsp;<span class="Operator">$</span><span class="Identifier">db</span><span class="Type">-&gt;</span>prepare<span class="Delimiter">(</span>'<span class="String">insert ignore into log (repo,commit,message,`date`) values</span>'&nbsp;<span class="Operator">.</span>&nbsp;<span class="Function">implode</span><span class="Delimiter">(</span>'<span class="String">,</span>',<span class="Operator">$</span><span class="Identifier">v</span><span class="Delimiter">))</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Statement">try</span>&nbsp;<span class="Delimiter">{</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Conditional">if</span>&nbsp;<span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">stmt</span><span class="Delimiter">)</span>&nbsp;<span class="Delimiter">{</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Conditional">if</span>&nbsp;<span class="Delimiter">(</span><span class="Operator">!</span><span class="Operator">$</span><span class="Identifier">stmt</span><span class="Type">-&gt;</span>execute<span class="Delimiter">(</span><span class="Operator">$</span><span class="Identifier">b</span><span class="Delimiter">))</span>&nbsp;<span class="Statement">throw</span>&nbsp;<span class="PreProc">new</span>&nbsp;<span class="Function">PDOException</span>;;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span>&nbsp;<span class="Conditional">else</span>&nbsp;<span class="Statement">Throw</span>&nbsp;<span class="PreProc">new</span>&nbsp;<span class="Function">PDOException</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span>&nbsp;<span class="Statement">catch</span>&nbsp;<span class="Delimiter">(</span><span class="Function">PDOException</span>&nbsp;<span class="Operator">$</span><span class="Identifier">e</span><span class="Delimiter">)</span>&nbsp;<span class="Delimiter">{</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="Function">mail</span><span class="Delimiter">(</span>'<span class="String">EMAIL</span>','<span class="String">Commit did not reach db</span>',<span class="Operator">$</span><span class="Identifier">e</span><span class="Type">-&gt;</span>getMessage<span class="Delimiter">())</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="Delimiter">}</span><br />
<span class="Delimiter">}</span><br />
<span class="Delimiter">?&gt;</span><br />
</code></div>
<p>So basically we&#8217;re extracting the log data we need, doing some funky stuff to handle multi-line commit messages (I like to store lots of details as my subject messages tend to be a bit vague!). Other than that, if you&#8217;re familiar with PHP, the above should be pretty self-explanatory. If it&#8217;s not, hit the comments and I&#8217;ll explain things.</p>
<p><small>I&#8217;ve only been using this a little while, but it seems to work very well. If you use it and stumble across any bugs, I&#8217;d love to know about them!</small></p>
<p><strong>Update:</strong> I&#8217;ve today realised that git log only logs the currently-selected branch, or master on a bare repo so I&#8217;ve added the <strong>&#8211;all</strong> switch to git log so I can get the logs for every branch. Most of it&#8217;s just &#8220;Merged blah&#8221; but that means it can be filtered easily and I&#8217;d rather have everything and need to filter than be missing something important.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/store-git-activity-in-mysql-with-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Managing multiple working copies with git</title>
		<link>http://jspr.tndy.me/managing-multiple-working-copies-with-git/</link>
		<comments>http://jspr.tndy.me/managing-multiple-working-copies-with-git/#comments</comments>
		<pubDate>Thu, 22 Jul 2010 08:51:16 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[gtd]]></category>
		<category><![CDATA[script life]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2175</guid>
		<description><![CDATA[We have a CMS at Buffalo, which we have deployed to several servers for a few of our clients. Until recently, it was only two separate servers and was relatively easy to manage with Subversion (though slightly cumbersome &#8211; svn ci -m &#8220;blah&#8221;; ssh server; cd /site; svn up; exit; ssh server2; cd /site; svn [...]]]></description>
			<content:encoded><![CDATA[<p>We have a CMS at Buffalo, which we have deployed to several servers for a few of our clients. Until recently, it was only two separate servers and was relatively easy to manage with Subversion (though slightly cumbersome &#8211; svn ci -m &#8220;blah&#8221;; ssh server; cd /site; svn up; exit; ssh server2; cd /site; svn up;) but now that we&#8217;re deploying the same CMS to 8 servers, simple changes and bug fixes are becoming a pain in the ass to deploy.</p>
<p>I&#8217;ve been tentatively looking into git for quite some time now. I&#8217;m incredibly cautious of the bleeding edge when it comes to how I make money because I prefer to have things that I can rely on and will serve me well than keep up with the latest fads. That&#8217;s not to say that I&#8217;m not interested in all the cool new jazz, and I keep up as much as family life permits, but I don&#8217;t dive in and adopt without learning and understanding everything I need first. Sensible? Yes. I&#8217;m surprised at how many people flaunt this ethos.</p>
<p>That being said, git has proved itself invaluable to me in the last 4 months. The way it encourages you to work is great. I do all changes on branches then merge them into master and remove branches to keep everything clean. I also have $deploy-staging and $deploy-live (where $deploy is a working copy) so that I can manage configuration for each working copy. This probably isn&#8217;t git best practice, but I&#8217;ve found it to be incredibly convenient. I work on up to 20 different sites in any week, so being able to merge changes and conflicts for live stuff locally saves me headaches galore. No-brainer. My git workflow goes something like this:</p>
<div class="code"><code><span class="Identifier">git</span>&nbsp;checkout <span class="Special">-b</span>&nbsp;hotfix-phperror<br />
<span class="Comment"># do some work</span><br />
<span class="Identifier">git</span>&nbsp;commit <span class="Special">-am</span>&nbsp;<span class="Constant">&quot;Hotfix for PHP Error fixed&quot;</span><br />
<span class="Identifier">git</span>&nbsp;checkout master<br />
<span class="Identifier">git</span>&nbsp;merge hotfix-phperror<br />
<span class="Comment"># resolve any conflicts</span><br />
<span class="Identifier">git</span>&nbsp;checkout staging<br />
<span class="Identifier">git</span>&nbsp;merge master<br />
<span class="Comment"># test on staging - everything OK</span><br />
<span class="Identifier">git</span>&nbsp;checkout live<br />
<span class="Identifier">git</span>&nbsp;merge master<br /></code></div>
<p>That might seem like quite a bit of work, but it keeps everything tidy. Unfortunately, it does mean that if things fail on staging, I still have the history of it in master. I suppose it would make more sense to merge the hotfix into staging, then merge that into live, then merge everything into master and tag once it&#8217;s verified working to keep it clean, but either way, this tip will work.</p>
<p><em>I&#8217;m under the impression that git frowns upon what I&#8217;m about to recommend, but it works so well that I can hardly ignore it! One proviso for this is that all the working copies you push to have to be the same. Lucky for us, the configuration for our CMS is held by the live site (separate repository) rather than the CMS itself, so each working copy is the same.</em></p>
<p>With the above in mind, I&#8217;m going to assume the following:</p>
<p>• You&#8217;ve got a central, bare repository to push to<br />
• Your live branch (identical for all working copies) is tracked from said central repository</p>
<p>First off, you have to make sure that your local working copy has all remotes available. Obviously your bare repository will be available because that&#8217;s how you&#8217;re doing things anyway, but we also need working copies. You can check that everything&#8217;s there by running a quick <code>git remote</code>. If you&#8217;re all good, then we can start pushing code around.</p>
<p>As I said before, git doesn&#8217;t seem to like this sort of thing. If you were to try and push to one of your working copies where the checked out branch is the one you&#8217;re pushing to, git will whine at you and preserve the changes you push so that you can stash any local differences. You can force this by changing to the directory and running <code>git reset --hard</code> or <code>git stash</code>, but that defeats the purpose of this so we need a workaround.</p>
<p>Luckily, git gives you all the hooks you need and more for this. We&#8217;ll be creating a post-update hook that will force-update your push. With that in mind, cd to the root of your working copy on one of your remotes and do something like:</p>
<div class="code"><code><span class="Comment">#!/bin/sh</span><br />
<span class="Statement">cd</span>&nbsp;..<br />
<span class="Identifier">env</span>&nbsp;<span class="Special">-i</span>&nbsp;git reset <span class="Special">--hard</span><br />
</code></div>
<p>In .git/hooks/post-update and chmod it executable.</p>
<p>Now, when you push to the repository git will moan at you, but this hook will run and force all your changes. Awesome.</p>
<p>In the warning message git spits out, it threatens that new versions of git will auto-reject pushes to checked out branches, so a quick run of:</p>
<div class="code"><code><span class="Identifier">git</span>&nbsp;config <span class="Special">--set</span>&nbsp;receive.denyCurrentBranch <span class="Constant">&quot;ignore&quot;</span><br /></code></div>
<p>Will shut git up and have it doing what you tell it to. I suppose the reason it does this is it assumes that someone is working in that working copy and they don&#8217;t want you frivolously overwriting their code. Luckily, we know what we&#8217;re doing is safe so there&#8217;s no harm in doing this. Do remember, though, that all changes made in the working copy <strong>will be overwritten</strong> when you push using this method, so don&#8217;t work on live working copies. Don&#8217;t do that anyway, but definitely don&#8217;t do it here!</p>
<p>Now, you can do the following:</p>
<div class="code"><code><span class="Identifier">git</span>&nbsp;remote add remote-alias ssh://root@blah/path/to/working/copy<br />
<span class="Identifier">git</span>&nbsp;fetch<br />
<span class="Identifier">git</span>&nbsp;push remote-alias live<br />
</code></div>
<p>In all the feedback, you&#8217;ll see something like &#8220;HEAD is now at 0d5431b HELLO!!!&#8221; (the hash and first line of your last commit message) which lets you know that things have worked.</p>
<p>Now you&#8217;re able to push to remotes without complaints, from the comfort of your local working copy, with a bit of scripting you can deploy your local live branch to all its remote locations with a little bit of scripting:</p>
<div class="code"><code>for remote in `git remote`; do git push $remote branchname; done;</code></div>
<p>For future reference, we&#8217;ll assume that this isn&#8217;t the only time you&#8217;ll do this, so save the following as a script in your path somewhere (I call it git-rpush):</p>
<div class="code"><code><span class="Identifier">for</span>&nbsp;remote in <span class="PreProc">`</span><span class="Identifier">git</span>&nbsp;remote<span class="PreProc">`</span><span class="Statement">;</span>&nbsp;<span class="Identifier">do</span>&nbsp;git push <span class="Type">$remote</span>&nbsp;$@<span class="Statement">;</span>&nbsp;<span class="Identifier">done</span>;<br /></code></div>
<p>Which you can call with <code>git rpush live</code>. Assuming that all your working copies have the config set and the hook installed, you can now push a change to all of your local repositories at once without having to remember which ones you actually need to push to, then spending half an hour SSH-ing all over the place to do it.</p>
<p>I don&#8217;t know about you, but that&#8217;s just saved me a crap-load of time. Hope it helps someone!</p>
<p>Coincidentally, if you do need this to be locally configurable, you can easily check out your branch and create a patch for configuration, then add the patch removal and application into hooks. I would do something like the following (assuming you already have your patch):</p>
<div class="code"><code><span class="Comment">#!/bin/sh</span><br />
<span class="Statement">cd</span>&nbsp;..<br />
<span class="Identifier">env</span>&nbsp;<span class="Special">-i</span>&nbsp;git apply <span class="Special">-R</span>&nbsp;config.patch<br />
</code></div>
<p>In .git/hooks/pre-update and chmod executable</p>
<p>This will remove your config so that no conflicts occur during the update. All you need is to re-apply the patch in the post-update, after you&#8217;ve done the reset. Insert the following:</p>
<div class="code"><code><span class="Identifier">git</span>&nbsp;apply config.patch<br />
</code></div>
<p>Your config will be preserved. You can even keep these patches in version control, just change the name for all your remotes and update your hooks accordingly. Then enter my competition on how easy you can make your life by scripting it!</p>
<p>Any and all questions and improvements are welcome and appreciated.</p>
<p>I would like to thank the <a href="http://debuggable.com/posts/git-tip-auto-update-working-tree-via-post-receive-hook:49551efe-6414-4e86-aec6-544f4834cda3">following post</a> for hand-holding through the intricacies of what I was trying to achieve with hooks. So thanks!</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/managing-multiple-working-copies-with-git/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>trying an un-annoying lightbox</title>
		<link>http://jspr.tndy.me/trying-an-un-annoying-lightbox/</link>
		<comments>http://jspr.tndy.me/trying-an-un-annoying-lightbox/#comments</comments>
		<pubDate>Sun, 23 May 2010 00:25:55 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[flickr]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[lightbox]]></category>
		<category><![CDATA[mootools]]></category>
		<category><![CDATA[photos]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2104</guid>
		<description><![CDATA[Lightboxes are really irritating. No users I ever speak to like them, but all website owners love them &#8211; communication problems there, maybe? Here&#8217;s why I don&#8217;t like lightboxes: They usually override keyboard functionality. I use my escape key to clear fields and I use my arrow keys to navigate pages &#8211; don&#8217;t overwrite that. [...]]]></description>
			<content:encoded><![CDATA[<p>Lightboxes are really irritating. No users I ever speak to like them, but all website owners love them &#8211; communication problems there, maybe?</p>
<p>Here&#8217;s why I don&#8217;t like lightboxes:</p>
<ul>
<li>They usually override keyboard functionality. I use my escape key to clear fields and I use my arrow keys to navigate pages &#8211; don&#8217;t overwrite that. I know best, not you.</li>
<li>They&#8217;re unnecessarily schmancy and animated &#8211; if I opened a lightbox, chances are I want to look at a picture and nothing else. Leave me alone otherwise. I don&#8217;t want to see your great &#8220;close&#8221; icon or any of that crap so just drop it!</li>
<li>Some are nearly impossible to close</li>
</ul>
<p>There are probably a bunch more reasons, but you get the idea.</p>
<p>My lightbox stays out of your way. It doesn&#8217;t override any standard keyboard behaviour and it doesn&#8217;t use visual fluff to irritate you. What it does do is open a big version of the picture you clicked on so you can see it in more detail. When you&#8217;re done, it closes. Here&#8217;s how you use it:</p>
<ul>
<li>Click a flickr image to open the lightbox (this might be the last time you need your mouse/trackpad)</li>
<li>j = next</li>
<li>k = previous</li>
<li>o = open the flickr page for this image</li>
<li>q = close the lightbox</li>
<li>You can also click anywhere to close the light when it&#8217;s open</li>
</ul>
<p>If you&#8217;re wondering why j,k,q; use Vim for a day. I would&#8217;ve used e to open, but who&#8217;s going to remember that?</p>
<p>I&#8217;m probably going to implement a resizing thing for people with small monitors so that screens don&#8217;t get flooded &#8211; if this affects you, please tell me whether you&#8217;d like the process to automatically detect your browser size, or whether you&#8217;d prefer to handle it yourself. I&#8217;m aware that this will affect basically all portrait images &#8211; this will probably make me make it automatic. Pictures are pretty pointless if you have to scroll, but I&#8217;m tired now and can&#8217;t be bothered.</p>
<p>If you want the code for this, view source and find the javascript yourself. There&#8217;s a PHP file to make Flickr API calls, but that encapsulates my API credentials. If you can&#8217;t figure out what to do here, holla so we can all have a good laugh.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/trying-an-un-annoying-lightbox/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>that&#8217;s a big one</title>
		<link>http://jspr.tndy.me/thats-a-big-one/</link>
		<comments>http://jspr.tndy.me/thats-a-big-one/#comments</comments>
		<pubDate>Fri, 21 May 2010 08:17:17 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[huge commit]]></category>
		<category><![CDATA[svn]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2081</guid>
		<description><![CDATA[Transmitting file data &#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;. &#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;. &#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;. &#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;. &#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.]]></description>
			<content:encoded><![CDATA[<p>Transmitting file data &#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.<br />
&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.<br />
&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.<br />
&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.<br />
&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/thats-a-big-one/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>I must write out 1000 times&#8230;</title>
		<link>http://jspr.tndy.me/i-must-write-out-1000-times/</link>
		<comments>http://jspr.tndy.me/i-must-write-out-1000-times/#comments</comments>
		<pubDate>Fri, 30 Apr 2010 14:19:00 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[serveradmin]]></category>
		<category><![CDATA[unix]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2054</guid>
		<description><![CDATA[Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets permissions Copying directories resets [...]]]></description>
			<content:encoded><![CDATA[<pre>
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
Copying directories resets permissions
</pre>
<p>Not 1000, but you get the idea. Fuck.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/i-must-write-out-1000-times/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>impress yourself</title>
		<link>http://jspr.tndy.me/impress-yourself/</link>
		<comments>http://jspr.tndy.me/impress-yourself/#comments</comments>
		<pubDate>Mon, 19 Apr 2010 16:28:20 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2023</guid>
		<description><![CDATA[Just recently, I realised how important impressing yourself is when trying to maintain enthusiasm with your job. Doesn&#8217;t really matter what you do (as long as you care!), but things can go stale if you keep doing the same thing over and over, and never really provoking an &#8220;AWESOME! I did that!&#8221; reaction. Even if [...]]]></description>
			<content:encoded><![CDATA[<p>Just recently, I realised how important impressing yourself is when trying to maintain enthusiasm with your job. Doesn&#8217;t really matter what you do (as long as you care!), but things can go stale if you keep doing the same thing over and over, and never really provoking an &#8220;AWESOME! I did that!&#8221; reaction. Even if it&#8217;s only something small (at 8 hours a day, 5 days a week, little things can be fairly impressive!), it can give you a needed boost to productivity (ugh), enthusiasm and general satisfaction with work, life and all that!</p>
<p>Personally, I get my kick from cleaning really crappy data. Lucky for me, I&#8217;ve had a lot of crappy data to clean recently, so I&#8217;ve been able to flex my XPath, Regex, semi-complex MySQL and image manipulation muscles relatively frequently. If you&#8217;re as much of a fanatical cleaner and hoarder as I am, cleaning data is probably for you. Grab a 50MB CSV and try to programmatically make sense of it. It&#8217;s fun! I promise.</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/impress-yourself/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Unit testing</title>
		<link>http://jspr.tndy.me/unit-testing/</link>
		<comments>http://jspr.tndy.me/unit-testing/#comments</comments>
		<pubDate>Tue, 13 Apr 2010 21:27:31 +0000</pubDate>
		<dc:creator>Jasper</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[phpunit]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[unit testing]]></category>

		<guid isPermaLink="false">http://jspr.tndy.me/?p=2013</guid>
		<description><![CDATA[This is something that&#8217;s been in my peripheral vision for some time now. I&#8217;ve periodically been struggling to see the need for unit testing in general, without any justification. I can obviously see the benefit of making sure that your code all works and, seeing as I frequently work on ecommerce sites, I can see [...]]]></description>
			<content:encoded><![CDATA[<p>This is something that&#8217;s been in my peripheral vision for some time now. I&#8217;ve periodically been struggling to see the need for unit testing in general, without any justification. I can obviously see the benefit of making sure that your code all works and, seeing as I frequently work on ecommerce sites, I can see it causing far fewer headaches when making changes to purchase processes &#8211; simply being able to run a script to see if anything I changed works right.</p>
<p>However, one drawback of unit testing as a concept is that computers are, as a rule, infallible when it comes to this sort of thing. Humans, on the other hand, are completely fallible. If I make a change to some code in my checkout process that has a knock-on effect I didn&#8217;t count on in my unit testing (like changing the way delivery is calculated/stored/whatever), I wouldn&#8217;t necessarily foresee there being any problems in the email that gets sent after a transaction is completed (that may seem pretty esoteric, but it happened today). Part of this issue arose because I didn&#8217;t write the original site, and there was no way for me to detect that this could&#8217;ve caused a problem without going through every file on the site and manually checking that there was a problem, but another part <em>could</em> have been solved if a good unit testing framework was in place.</p>
<p>Now, when I say &#8220;good&#8221;, what I really mean is &#8220;comprehensive&#8221;. Anyone can write a good unit testing framework and here&#8217;s how, in my opinion. You test /every single dependency/. Everything. Bar nothing. If unit testing could&#8217;ve solved my problem today, it would&#8217;ve been done a little something like this (not exactly like this because the code on this site is largely a mish-mash of procedural and I-don&#8217;t-quite-understand-OOP-so-I&#8217;ll-give-it-my-best-shot &#8211; I assume it would&#8217;ve been very difficult to express this as a unit test).</p>
<p>Say I have a method that outputs this email that gets sent. I would write a unit test to capture the output of that method (when supplied with sample data from my database table, for a little extra reliability). I would then take this method and write my own version of it inside my unit test, so that I know it&#8217;s correct and compare the output of the two methods. If they differ, I would (PHPUnit) assertSame(legacy_method_output,my_method_output), and the test would pass if I hadn&#8217;t fucked it up. Now, the perceptive among you will have spotted something wrong here. In order to write this unit test, I would first have had to know about this email&#8217;s pre-disposition to causing problems in the first place. This, in this case, is the entire battle. Furthermore, I would have had to completely rewrite the method, solely to test that the old method hadn&#8217;t been broken in some way. For future testing purposes, this is really useful but, chances are I&#8217;d just rewrite the method and move on.</p>
<p>All of this isn&#8217;t to say that unit testing is fundamentally flawed. Far from it, in fact. I can see that it&#8217;s an incredibly useful tool for documentation and future-updates. When I come back to code that already has a unit testing framework, I&#8217;d simply make my alterations, run my unit tests until they all pass, then write some more to cater for my currently augmented feature-set. I would then know (with only a shadow of a doubt) that everything is brilliant and nothing is going to get fucked up when I put my code live. That&#8217;s got to be worth it! Not only the above, but unit tests seem to me to serve as excellent developer documentation. If you want to know how something&#8217;s supposed to work, never mind asking the project manager or client (who probably don&#8217;t know, and don&#8217;t really care!), you can just look and the guy who wrote it will have told you! Brilliant.</p>
<p>The only (and sadly, this is a pretty substantial &#8220;only&#8221;) problem is time (as with everything, I guess). You get all of this great peace of mind, but you have to put time into it. And it&#8217;s not just a small amount of time either, it&#8217;s a lot of time. Once you&#8217;ve written all your code (maybe doing it as you go along &#8211; 30 minutes a day or something) you then have to write tests to try and break it, and cater for every potential outcome that could arise from user or developer input of the code you wrote. That&#8217;s a large investment of time. I can&#8217;t quite decide if the net gain is quite worth the input, but I&#8217;m tipping towards a yes. I spend a lot of time a bit stressed about whether a modification I&#8217;ve made is going to cause unexpected problems, and I&#8217;d really like to be able to be more confident with this. I&#8217;m also one of those people who likes doing little scripty things, and gets a great sense of satisfaction from ticking boxes and watching &#8220;make test&#8221; throw out 100% success. Maybe this is for me, after all!</p>
]]></content:encoded>
			<wfw:commentRss>http://jspr.tndy.me/unit-testing/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

