<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6560382540998172563</id><updated>2011-08-24T06:55:57.487-07:00</updated><category term='ruby'/><category term='rest'/><category term='linux'/><category term='fanboy'/><category term='webgui'/><category term='filesystems'/><category term='tech industry'/><category term='cloud computing'/><category term='git'/><category term='php'/><category term='ec2'/><category term='rails'/><category term='howto'/><category term='vendors'/><category term='mac'/><category term='example'/><category term='browser wars'/><category term='code'/><category term='fail'/><category term='Perl'/><category term='communication'/><category term='CiviCRM'/><category term='ipv6'/><title type='text'>Nerdy Adventures of Wes</title><subtitle type='html'>Foibles and fumbles in the land of IT, web app development, cloud computing, and open source advocacy.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>10</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-3488865467924788991</id><published>2010-11-26T10:22:00.000-08:00</published><updated>2010-11-26T10:22:05.451-08:00</updated><title type='text'>Moved to Posterous</title><content type='html'>I'm merging this blog with my Posterous blog: &lt;a href="http://www.timetraveltoaster.com/"&gt;Time Travel Toaster&lt;/a&gt;. No more posts here.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-3488865467924788991?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/3488865467924788991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/11/moved-to-posterous.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/3488865467924788991'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/3488865467924788991'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/11/moved-to-posterous.html' title='Moved to Posterous'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-8480378380393231720</id><published>2010-05-25T11:22:00.000-07:00</published><updated>2010-05-26T05:28:28.411-07:00</updated><title type='text'>Keeping your skeletons in the closet when open sourcing code in git</title><content type='html'>In my day job (DNC Innovation Lab), my team and I received approval to open-source some code that was started well before I arrived there. It was all stored on an internal git server, so no one thought it would be that hard to do. You get cocky after slinging code around with git for awhile. That's what good tools do.&lt;br /&gt;&lt;br /&gt;Unfortunately there were already passwords, API keys, and other things we couldn't release publicly in our git commit history.&lt;br /&gt;&lt;br /&gt;No problem, we thought, we'll just take a snapshot of the code, remove the bits we can't open source, and then upload that to github as a new repo, devoid of all that messy history. Easy peasy.&lt;br /&gt;&lt;br /&gt;Er, not so much. The problem is we wanted to maintain our internal branch, complete with git history, but open up development on the open source version (which comprised &amp;gt;99% of all the code) to github and thus outside collaborators. So we were going to be pushing and pulling to/from github, as well as merging into our internal branch. We couldn't let non-open-source code leak into github, but we also needed to merge the open sources changes into the internal version.&lt;br /&gt;&lt;br /&gt;Git took a look at these two branches and decided they didn't have anything to do with each other because they had no common ancestry in the commit history. This was the appropriate response from git because we had purposefully removed the commit history of the github version.&lt;br /&gt;&lt;br /&gt;I tried various methods of merging the two codebases, but git always generated conflicts left and right because it was attempting to merge two almost-but-not-quite identical codebases with no common ancestor commits.&lt;br /&gt;&lt;br /&gt;Here's how I solved it (I hope). Let's say the internal branch is called "internal" and the open source branch is called "opensource". The commit history of the open source branch is one über-commit (X) followed by a couple small changes (Y &amp;amp; Z), which we'll represent as X &amp;lt;-- Y &amp;lt;-- Z. The commit history of the internal branch is pretty long, so we'll just abbreviate it as the three most recent commits, A &amp;lt;-- B &amp;lt;-- C. So here's how our two branches start out:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;internal: &amp;nbsp; A &amp;lt;-- B &amp;lt;-- C&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;opensource: X &amp;lt;-- Y &amp;lt;-- Z&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;I decided to try merging the opensource branch with the internal branch using the "ours" merge strategy on the X commit. This merge strategy just discards the changes in the other branch and considers the two branches merged anyway. So I ran:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;git checkout internal&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;git merge -s ours (sha1 of the X commit)&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Then my commit history looked like this:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;internal: &amp;nbsp; A &amp;lt;-- B &amp;lt;-- C &amp;lt;-- D&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; /&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;opensource: &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; X &amp;lt;-- Y &amp;lt;-- Z&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;The new D commit was the "merge" between C and X, but a couple quick git diffs showed that my working tree was exactly the same as the C commit, and thus had discarded the changes from X. This is what I wanted because X represented almost the exact same codebase, except for the minor changes required to open source it.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Now I was in a position to merge new commits to the opensource branch into the internal branch. I ran this (still on the internal branch):&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;git merge opensource&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Then my commit history looked like this:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;internal: &amp;nbsp; A &amp;lt;-- B &amp;lt;-- C &amp;lt;-- D &amp;nbsp; &amp;nbsp;&amp;lt;-- &amp;nbsp; &amp;nbsp;E&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; / &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; /&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;opensource: &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; X &amp;lt;-- Y &amp;lt;-- Z&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;So now I can merge new commits to the opensource branch (coming from github or others on my internal team) into the internal branch, and changes that are internal only can be made directly to that branch.&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;I'll update this post if I run into any breakage due to this.&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-8480378380393231720?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/8480378380393231720/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/05/keeping-your-skeletons-in-closet-when.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/8480378380393231720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/8480378380393231720'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/05/keeping-your-skeletons-in-closet-when.html' title='Keeping your skeletons in the closet when open sourcing code in git'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-2235109636178794091</id><published>2010-02-04T14:31:00.000-08:00</published><updated>2010-02-04T15:01:12.429-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='example'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='CiviCRM'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Setting and getting custom field values in CiviCRM hooks</title><content type='html'>CiviCRM isn't always the most predictable codebase. Recently I needed to get and set some custom field values in a hook I was writing. The hook's job was to calculate some custom field values and create some contact references when a contribution was created or updated. As always, dlobo was a huge help (he's the CiviCRM guru, find him in #civicrm on Freenode). Here's what I did to set a couple of custom fields in my _pre hook:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$custom_fields = array('foo' =&gt; 'custom_1', 'bar' =&gt; 'custom_2');&lt;br /&gt;function modulename_civicrm_pre ($op, objectName, $objectId, &amp;$objectRef) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if ($objectName != 'Contribution' || ($op != 'edit' &amp;&amp; $op != 'create')) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;$contribution_id = $objectId;&lt;br /&gt;&amp;nbsp;&amp;nbsp;require_once 'CRM/Core/BAO/CustomValueTable.php';&lt;br /&gt;&amp;nbsp;&amp;nbsp;$my_foo = 'blah';&lt;br /&gt;&amp;nbsp;&amp;nbsp;$my_bar = 'baz';&lt;br /&gt;&amp;nbsp;&amp;nbsp;$set_params = array('entityID' =&gt; $contribution_id,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$custom_fields['foo'] =&gt; $my_foo, $custom_fields['bar'] =&gt; $my_bar);&lt;br /&gt;&amp;nbsp;&amp;nbsp;CRM_Core_BAO_CustomValueTable::setValues($set_params);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And here's an example for retrieving some custom field values from the contact object in the same hook:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$custom_fields = array('contact_foo' =&gt; 'custom_3', 'contact_bar' =&gt; 'custom_4');&lt;br /&gt;function modulename_civicrm_pre ($op, objectName, $objectId, &amp;$objectRef) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if ($objectName != 'Contribution' || ($op != 'edit' &amp;&amp; $op != 'create')) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;// set the field names to 1 that we want to get back&lt;br /&gt;&amp;nbsp;&amp;nbsp;$get_params = array('entityID' =&gt; $objectRef['contact_id'],&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$custom_fields['contact_foo'] =&gt; 1, $custom_fields['contact_bar'] =&gt; 1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;require_once 'CRM/Core/BAO/CustomValueTable.php';&lt;br /&gt;&amp;nbsp;&amp;nbsp;$values = CRM_Core_BAO_CustomValueTable::getValues($get_params);&lt;br /&gt;&amp;nbsp;&amp;nbsp;$my_cfoo = $values[$custom_fields['contact_foo']];&lt;br /&gt;&amp;nbsp;&amp;nbsp;$my_cbar = $values[$custom_fields['contact_bar']];&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;So it's not ideal that you have to hard-code the custom field IDs; there should be a way to look them up (maybe there is). But it's not the worst thing in the world unless you're in the habit of destroying and recreating your custom fields from time to time. Probably you're not on a production system.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-2235109636178794091?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/2235109636178794091/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/02/setting-and-getting-custom-field-values.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/2235109636178794091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/2235109636178794091'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/02/setting-and-getting-custom-field-values.html' title='Setting and getting custom field values in CiviCRM hooks'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-2788764501820281886</id><published>2010-01-19T08:50:00.000-08:00</published><updated>2010-02-04T15:01:43.093-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fail'/><category scheme='http://www.blogger.com/atom/ns#' term='tech industry'/><category scheme='http://www.blogger.com/atom/ns#' term='communication'/><category scheme='http://www.blogger.com/atom/ns#' term='vendors'/><title type='text'>A list of things technology vendors must never screw up</title><content type='html'>&lt;ol&gt;&lt;li&gt;Communication&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;That's it. Everything else is forgivable, as long as you tell me about it--see what I just did there? All of our communication should specify precisely what, when, how, and why. What are you doing? When are you doing it (or when are you updating me next)? How are you going to do it? Why are we doing this in the first place? I should be able to expect this from you, and you should be demanding it from me.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here are some examples:&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;If you're going to shut down my servers, you need to either be on the phone with me saying, "OK, I'm shutting this down now. Is that alright?" or you need to be staring intently and gravely at a ticket that says, "Yes, you may shut down that server on [date] at [time]," and have a calendar and clock nearby that are set correctly and correspond to [date] and [time]. If any of the above criteria aren't met, don't shut down my effing servers! I'm looking at you, &lt;a href="http://www.rackspace.com/"&gt;Rackspace&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;If I can't access my servers but they're not *down* per se, that's still an emergency. I'm opening tickets, calling, and e-mailing you and you're taking your sweet time to get back to me, and even then you just say, "We're working on a new release of our software, so all our techs are busy. But they'll get to your issue as soon as they can." Nope, that doesn't work. But lucky for you, &lt;a href="http://www.rightscale.com/"&gt;Rightscale&lt;/a&gt;, it's an easy fix. You should have said, "Hey, sorry this is taking so long. We're in the middle of a release of our software. I'll go and find out exactly when someone can get to your issue and call you back right away." You don't even have to do anything faster than you would have, &lt;b&gt;just freaking tell me!&lt;/b&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;This is pretty simple, but if I had to give the tech industry a grade on it right now, it would be a F---. Fail. Whale.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's the most frustrating part: My clients would say that I'm the pot calling the kettle black. They want better communication from me when something is down or inaccessible. But usually the reason I can't give them that is I'm spending all of my time wrestling with you, the vendor, to get some kind of real information. If you were communicating with me, I could be passing that along to my clients and we could all get back to actually fixing the problem. Wouldn't that be nice?&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-2788764501820281886?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/2788764501820281886/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/01/list-of-things-technology-vendors-must.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/2788764501820281886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/2788764501820281886'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/01/list-of-things-technology-vendors-must.html' title='A list of things technology vendors must never screw up'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-6085804009011429671</id><published>2010-01-14T21:44:00.000-08:00</published><updated>2010-02-04T15:02:08.435-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rest'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><title type='text'>RESTful web services for value objects</title><content type='html'>OK, this one's gonna be pretty nerdy, even for me. Yeah.&lt;br /&gt;&lt;br /&gt;In the world of object-oriented software design, we talk about things we're modeling as falling into 1 of 2 categories: Entities and Value Objects. Entities have identity separate from their constituent attributes, while Value Objects do not. So I, for example, am an Entity, but the color magenta is a Value Object. Even if you list my attributes (first name: Wes, last name: Morgan, hair color: blond, eye color: hazel, height: 5'11"), that doesn't fully encompass who I am because there is probably at least one other guy out there matching that same description. Freaky Friday. But if you were trying to find &lt;i&gt;me&lt;/i&gt;, that other guy just won't do.&amp;nbsp;Magenta, however, is a color that can be represented by 3 red, green, and blue values, for example. Magenta's magenta. This is nice because you can create an immutable, throw-away magenta instance of a Color class to represent it in your code. Decide you like chartreuse better? Throw away your magenta instance and instantiate a new Color object for chartreuse. This has several handy side-effects for your code. Entities require much more care and feeding. I should know, I am one. And so is that jerk imposter whom I will crush.&lt;br /&gt;&lt;br /&gt;Anyway! REST works very well on Entities. You have a URL like http://server.org/people/34 and you call various HTTP methods on person #34 (GET, PUT, POST, DELETE). How rude of you. But it all works. Wam, bam, thank ya ma'am.&lt;br /&gt;&lt;br /&gt;But what about Value Objects? If you wanted to write an RGB to HTML hex value web service that converted colors from red, green, and blue values to their equivalent HTML hex value, how would make that RESTful? What's the resource? What operations do you map the HTTP verbs to? Here's a real-world example I ran into this week that highlights the conundrum and how I solved it:&lt;br /&gt;&lt;br /&gt;I have a piece of software that converts ZIP codes into City, State pairs. This is how we in the U.S. of A. do postal codes. It can also take full street addresses and sanitize them and find out their 9-digit ZIP code. For those outside the U.S., the 9-digit ZIP code is extra special because it can tell you exactly where that address is geographically, but almost no one knows the final four digits of their code. So having software that can derive that code from their street address (which they do know) is super handy. My mission was to wrap this software in a Ruby on Rails layer to create RESTful web services out of these functions.&lt;br /&gt;&lt;br /&gt;Step 1. ZIP -&amp;gt; City, State : ZIP codes are a great example of a Value Object. The ZIP code of my office in downtown Denver, CO is 80202. 80202 is the same as 80202, even though they're at different places on this page. You don't care which one I typed first, they're interchangeable. So it would be silly to create a RESTful web service that did something like this: GET /zips/89 which would return the ZIP code w/ id 89 and the associated city and state. Let's say it's 73120 in Oklahoma City, OK. Why assign ZIP codes an id number? Why not just do this: GET /zips/73120 and have that return Oklahoma City, OK? So that's what I did. The value *is* the id. That works great, moving on.&lt;br /&gt;&lt;br /&gt;Step 2. Address -&amp;gt; Better Address (incl. the magical 9-digit ZIP code) : Now things get trickier. An address is still a value object. 123 Main St., Missoula, MT is the same as 123 Main St., Missoula, MT. We don't need to assign id's to these objects, they're just the sum of their parts. But unlike ZIP codes, more than one attribute is required before you can say you have an Address object. You need 1-2 lines of house number, street, apartment number, etc. information, then a city, state, and 5-9 digit ZIP code. That's a bit harder to put into a URL. Ideally we want to be able to label the different attributes so we can easily keep track of what's what. Something like this:&lt;br /&gt;&lt;code&gt;GET /addresses/123%20Main%20St.%20Missoula,%20MT%2059801&lt;/code&gt; &lt;i&gt;...could&lt;/i&gt;&amp;nbsp;work, but it's kind of a hassle because you then have to write a parsing routine on the server side. It would be nicer if you could just label the components of the address when you pass it in. Query parameters to the rescue! Here's what I did:&lt;br /&gt;&lt;code&gt;GET /addresses/1?addr1=123%20Main%20St.&amp;amp;addr2=&amp;amp;city=Missoula&amp;amp;state=MT&amp;amp;zip=59801&lt;/code&gt;&lt;br /&gt;Now I can very easily pass this address into my sanitizer and get back the corrected version w/ the 9-digit ZIP. The "1" in the URL is a pseudo-id placeholder. If it bothers you, you could just as easily put "address" or "thisone" or "foo" there. But &lt;b&gt;don't&lt;/b&gt;&amp;nbsp;put "get" or "lookup" or any other verb there! That's not RESTful. Only the HTTP verb should define the action being taken on the resource.&lt;br /&gt;&lt;br /&gt;So yeah, that's how I did it. I think it's still pretty RESTful. It only uses GET, but that's all you really need for read-only Value Objects. It would probably be better to do something like GET /zips/90210/city or GET /zips/80218/state, or maybe even GET /zips/73120/city_state to avoid the double call in the common case of wanting the city &lt;i&gt;and&lt;/i&gt; state. I still don't really know of a cleaner way to do the address sanitizer piece. It works, but it feels a bit hackish.&lt;br /&gt;&lt;br /&gt;I would like it if smarter types chimed in and critiqued my approach. Because this is all just, like, my opinion, man.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-6085804009011429671?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/6085804009011429671/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/01/restful-web-services-for-value-objects.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/6085804009011429671'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/6085804009011429671'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2010/01/restful-web-services-for-value-objects.html' title='RESTful web services for value objects'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-4062345477673213550</id><published>2009-11-22T18:32:00.000-08:00</published><updated>2009-11-22T18:33:11.224-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ipv6'/><category scheme='http://www.blogger.com/atom/ns#' term='ec2'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud computing'/><title type='text'>IPv6's Killer App (Finally): Cloud Computing</title><content type='html'>I just submitted this to IT World. Let's see if they deem it worth publishing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="MsoNormal"&gt;IPv6 has been an approved standard for over a decade now. But its adoption has suffered from lack of a compelling reason to deploy it. Since the future scalability of the Internet depends on our adopting it soon, we should be on the lookout for so-called “killer applications” where its benefits over IPv4 can help us solve real world problems today. That will help get IPv6 rolled out before we run out of IPv4 addresses and experience all the problems that will cause. It is similar to incentivizing the use of renewable energy before cheap fossil fuels run out so the transition is less painful for all involved. This article discusses one such killer app for IPv6: Infrastructure-as-a-service cloud computing systems such as Amazon’s EC2.&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;Cloud computing solves many of the problems of maintaining and growing a complex server infrastructure. But it does so in a way that introduces a few new ones. One of the biggest challenges is IP addressing. IPv4, the underlying protocol powering the Internet as we know it today, as well as just about every other computer network we use on a daily basis, was not designed to accommodate the elastic nature of the cloud. Server instances need a way to find each other on an IP network, but it is not practical to assign every client account a static, publically routable IPv4 subnet, especially as they become scarcer. Depending on the cloud provider’s architecture, it is also sometimes advantageous not to use the public IPv4 address even when there is one available (due to performance and/or bandwidth cost considerations). In order to demonstrate how IPv6 can help with this, let’s start with an example scenario of how it works now on EC2.&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;a href="http://aws.amazon.com/ec2/"&gt;Amazon’s Elastic Compute Cloud (EC2)&lt;/a&gt; service is undeniably the leader in infrastructure-as-a-service cloud computing options. When you launch an instance, it is dynamically assigned an IPv4 address in the 10.0.0.0/8 private subnet. If Amazon is actually using a subset of that class A block, they’re not saying. So system administrators have to assume the entire 10.0.0.0/8 subnet is off limits for anything else (such as VPN subnets). A routable, public IPv4 address is also assigned so you can access the instance from outside Amazon’s network in a 1:1 NAT configuration. You can optionally request that this come from a pool of static addresses reserved for your use only. Amazon calls these Elastic IPs and charges for reserving them (though not while they’re actively in use). It may be tempting to think assigning an Elastic IP to each of your instances is a good solution, but there’s a downside.&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;When you access an EC2 instance from another instance (for example, when your PHP-enabled web server wants to contact your MySQL database server), it’s a good idea to use the 10.0.0.0/8 address whenever possible. This is because throughput will be higher (it’s a more direct route) and Amazon doesn’t charge for the bandwidth you use if the two instances are in the same data center (Amazon calls these Availability Zones). So Elastic IPs don’t help here, because they only apply to the public-facing side. However, it’s very difficult to coordinate which instance has what private IP since they are dynamically assigned at boot. A robust cloud infrastructure should always be designed so that individual instances can be thrown away (or lost) with little to no downtime of the application(s) being served. This means instances will be coming up and down all the time, so your application configurations cannot have hard-coded DNS entries or IPv4 addresses in them.&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;There are 3&lt;sup&gt;rd&lt;/sup&gt; party services that can help with this. &lt;a href="http://www.rightscale.com/"&gt;Rightscale&lt;/a&gt;, a cloud computing control panel service, recommends the use of &lt;a href="http://www.dnsmadeeasy.com/"&gt;DNS Made Easy&lt;/a&gt;. This service allows you to register dynamic DNS entries and then have your instances register their IPv4 addresses with those DNS entries when they boot up. This is a workable, if clumsy, solution. It gets trickier when you need to access these servers from outside Amazon’s network, since you cannot route packets to the 10.0.0.0/8 address over the public Internet or even a private VPN (unless you’re willing to route the entire 10.0.0.0/8 subnet over the VPN, but that will almost certainly cause you problems down the road when trying to connect to other private networks using that subnet, and there are many).&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;Wouldn’t it be nice if we had a networking protocol that was designed to work in this kind of environment? And wouldn’t it be even better if that protocol were the one poised to run the entire Internet in a few years anyway? Oh hi, IPv6. How long were you standing there? This is awkward…&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;The main reason Amazon has to charge for reserved Elastic IP addresses is because we’re running out of IPv4 addresses. Current estimates say that we will start experiencing the effects of IPv4 address exhaustion in 2010, and will almost certainly be entirely out of addresses by 2012. IPv6 on the other hand, has more addresses than we could ever hope to use up. This is because IPv6 addresses are 128 bits long, as opposed to 32 bits for IPv4 addresses. 32 bits gives you around 4 billion addresses. 128 bits gets you somewhere in the neighborhood of 4.5x10&lt;sup&gt;15&lt;/sup&gt; addresses for every observable star in the known universe. That’s a lot.&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;But the beauty of IPv6 isn’t just that there are more addresses, it’s also in how they are assigned. Current recommendations say that each subscriber to an IPv6 network should be assigned an entire /48 prefix. This means you have 16 bits to use for further subnetting and then 64 bits still left over for assigning addresses to your hosts. Every single one of the 65,000 or so subnets you’re given can hold the square of the entire existing 32-bit addressable IPv4 Internet. Whoa. And if you move your entire deployment to a new cloud hosting provider, only the 48-bit assigned prefix changes. Your subnets and host address assignments stay the same, and the IPv6 routing protocol is designed to inform everyone of the new home of your network so everything just keeps working.&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;Here’s how this could play out for EC2 if Amazon decided to roll out IPv6:&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Each EC2 client gets a /48 prefix.&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;When you launch an instance, you can either allow dynamic autoconfiguration of the IPv6 address or you can specify the subnet and host address you want to use.&amp;nbsp;This allows you to use the same static IP addresses for instances that are fulfilling the same role in your deployment. For example, if you are deploying a new database server instance, you can just assign its static version 6 IP to the new instance. (Ideally there would also be an option to assign / migrate IPv6 addresses after instances are running. This would allow you to sync up your new database to the old one before pointing your app servers at it, for example.)&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Any of these addresses are publically routable and accessible on the IPv6 Internet (but of course you can limit this w/ firewalling, presumably via an IPv6-capable version of Amazon’s security groups).&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;You can then use regular old static DNS because your IP addresses are under your control once again. Just assign AAAA records for the IPv6 addresses you’re using.&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;When administering your instances, just connect to their static IPv6 addresses.&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Configure your software in the cloud to connect to the other instances via IPv6 using the AAAA DNS hostnames.&lt;/li&gt;&lt;li&gt;There would be no private IPs vs. public IPs. You use the same addresses and DNS hostnames everywhere, and Amazon would waive the bandwidth charges if you stay inside your 48-bit prefix. You would no longer have to jump through hoops to get your software to use 10.0.0.0/8 addresses sometimes and publicly-routable addresses at other times.&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Amazon could still assign dynamic IPv4 addresses the same way they do now for legacy software that doesn’t yet support IPv6. This would also allow you to phase in IPv6.&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Amazon could still provide Elastic IPs for servers that need public IPv4 accessibility. In a typical web app configuration, this would just be the front-end load balancers.&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: Symbol;"&gt;&lt;span style="font: normal normal normal 7pt/normal 'Times New Roman';"&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;As the IPv6 Internet gets rolled out, your web app is already future-proofed because it has AAAA records in DNS and routable IPv6 addresses.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="MsoNormal"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;Cloud computing can benefit from IPv6 today, and since we need to move to IPv6 very soon anyway, it’s a win-win situation. Luckily Amazon has noticed this too (see the “Why don’t you use IPV6 addresses?” question on this page: &lt;a href="http://docs.amazonwebservices.com/AWSEC2/2009-08-15/DeveloperGuide/index.html?IP_Information.html"&gt;http://docs.amazonwebservices.com/AWSEC2/2009-08-15/DeveloperGuide/index.html?IP_Information.html&lt;/a&gt;), and they say they are investigating it. Here’s hoping they have something to announce in the next few months. Time’s a-ticking.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-4062345477673213550?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/4062345477673213550/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/11/ipv6s-killer-app-finally-cloud.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/4062345477673213550'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/4062345477673213550'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/11/ipv6s-killer-app-finally-cloud.html' title='IPv6&apos;s Killer App (Finally): Cloud Computing'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-4114484650631528216</id><published>2009-10-27T16:10:00.000-07:00</published><updated>2009-10-27T16:11:56.090-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='browser wars'/><title type='text'>Google Chrome's top tabs don't save any vertical screen real estate</title><content type='html'>An oft-cited benefit of tabs-at-the-top ala Google Chrome and beta builds of Safari 4 is that they save vertical screen real estate. They certainly &lt;i&gt;feel &lt;/i&gt;like they do. After all, the tabs are up in the title bar and the address and bookmark bars are the only thing between that and the web content you're browsing. However, I noticed something interesting today when I had Chrome open on top of Safari 4. See for yourself below.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_pKVg3t3iAhs/Sud9jD0V50I/AAAAAAAAA1E/3XJD8oTtvrk/s1600-h/Safari_vs_Chrome.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_pKVg3t3iAhs/Sud9jD0V50I/AAAAAAAAA1E/3XJD8oTtvrk/s640/Safari_vs_Chrome.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;It's the exact same size vertically, at least on Mac. Whoddathunkit?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-4114484650631528216?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/4114484650631528216/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/10/google-chromes-top-tabs-dont-save-any.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/4114484650631528216'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/4114484650631528216'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/10/google-chromes-top-tabs-dont-save-any.html' title='Google Chrome&apos;s top tabs don&apos;t save any vertical screen real estate'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_pKVg3t3iAhs/Sud9jD0V50I/AAAAAAAAA1E/3XJD8oTtvrk/s72-c/Safari_vs_Chrome.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-3862031025416055178</id><published>2009-10-23T23:16:00.000-07:00</published><updated>2009-10-23T23:17:03.042-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='filesystems'/><category scheme='http://www.blogger.com/atom/ns#' term='fanboy'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='mac'/><title type='text'>ZFS is dead, long live btrfs</title><content type='html'>&lt;a href="http://www.tuaw.com/2009/10/23/zfs-project-for-mac-os-x-discontinued/"&gt;Apple officially killed its dabble into the ZFS filesystem&lt;/a&gt; (that's redundant like saying "PIN number" but I ain't care).&lt;br /&gt;&lt;br /&gt;My hunch: They'll move from HFS+ to btrfs. What is btrfs? And, more importantly, how do you pronounce it?&lt;br /&gt;&lt;br /&gt;1. It's a super cool new filesystem being developed under the GPL, primarily for Linux. &lt;a href="http://btrfs.wiki.kernel.org/index.php/Main_Page"&gt;Official wiki&lt;/a&gt;.&lt;br /&gt;2. Like this: Butter Eff Ess. That's right, it's like budduh.&lt;br /&gt;&lt;br /&gt;Please please please! I wanna run Mac OS X 10.7 "Lion" on btrfs. That would be kick ass. Rarrrrr.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-3862031025416055178?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/3862031025416055178/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/10/zfs-is-dead-long-live-btrfs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/3862031025416055178'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/3862031025416055178'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/10/zfs-is-dead-long-live-btrfs.html' title='ZFS is dead, long live btrfs'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-201005574891426648</id><published>2009-10-13T16:24:00.000-07:00</published><updated>2010-02-04T15:02:50.124-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Perl'/><category scheme='http://www.blogger.com/atom/ns#' term='fail'/><title type='text'>DateTime::Format::Natural Fail</title><content type='html'>The Perl module DateTime::Format::Natural describes itself as: "&lt;code&gt;DateTime::Format::Natural&lt;/code&gt;&amp;nbsp;takes a string with a human readable date/time and creates a machine readable one by applying natural parsing logic."&lt;br /&gt;&lt;br /&gt;As such, it can parse strings like the following (taken straight from its own docs):&lt;pre&gt;march&lt;br /&gt;11 January&lt;br /&gt;dec 25&lt;br /&gt;feb 28 3am&lt;br /&gt;feb 28 3pm&lt;br /&gt;may 27th&lt;br /&gt;2005&lt;br /&gt;march 1st 2009&lt;br /&gt;October 2006&lt;br /&gt;february 14, 2004&lt;br /&gt;jan 3 2010&lt;br /&gt;3 jan 2000&lt;br /&gt;27/5/1979&lt;br /&gt;4:00&lt;br /&gt;17:00&lt;br /&gt;3:20:00&lt;/pre&gt;But you know what it &lt;strong&gt;can't&lt;/strong&gt; parse? This:&lt;br /&gt;&lt;pre&gt;12/03/2008 06:56:06 AM&lt;/pre&gt;Srsly?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-201005574891426648?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/201005574891426648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/10/datetimeformatnatural-fail.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/201005574891426648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/201005574891426648'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/10/datetimeformatnatural-fail.html' title='DateTime::Format::Natural Fail'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6560382540998172563.post-7779711427869270625</id><published>2009-09-30T11:03:00.001-07:00</published><updated>2009-09-30T11:41:46.994-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webgui'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><title type='text'>(How not to) Use git to maintain local changes to an upstream codebase</title><content type='html'>In my day job, we use an open-source content management system called &lt;a href="http://www.webgui.org/"&gt;WebGUI&lt;/a&gt;. They recently switched to git to manage their source code. "Great!" I thought, "Git's decentralized nature will make it super easy for me to maintain the local additions and modifications we've made to WebGUI over the years while still being able to update to new upstream releases as they come out."&lt;br /&gt;&lt;br /&gt;Yeah, right.&lt;br /&gt;&lt;br /&gt;&lt;span style="text-decoration: line-through;"&gt;I still haven't figured out how to do this correctly or easily&lt;/span&gt; (see update below). My first attempt was to branch at the v7.7.20 upstream release tag on the webgui-7.7 branch. My branch is called webgui-7.7-pin and I committed all my local additions and modifications to that branch. I then pushed it out to an EC2 instance so my WebGUI servers running in EC2 can pull their code from it when they launch. I committed a few more changes over time, and I even committed a couple of bug fixes upstream and to my local branch (because I want the bug fixes now, not in the next release). So far so good. But then 7.7.21 came out.&lt;br /&gt;&lt;br /&gt;I tried doing (on the webgui-7.7-pin branch):&lt;br /&gt;&lt;br /&gt;&lt;code&gt;git rebase v7.7.21&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That v7.7.21 tag name gets found by git on the webgui-7.7 branch and it rebases, or forward-ports, my local commits to the 7.7.21 tag. Git does this by rewinding all my commits, fast-forwarding from the v7.7.20 tag (the original branch point) and then re-applying my commits in order to that new tip. So I end up with exactly what I want, v7.7.21 but with my local changes applied on top of that. Then I tried to push it to my "cloud" remote (the EC2 instance serving my production servers). The push is rejected because it's not a fast-forward. As far as my cloud remote is concerned, my local copy of the webgui-7.7-pin branch is in chaos. It can't make heads or tails of my rebased code.&lt;br /&gt;&lt;br /&gt;I haven't found much useful advice on the web. I'm really surprised this isn't a more common workflow pattern for folks using git. But maybe it is and I'm just going about it all wrong. Hopefully someone will smack me with a clue stick soon!&lt;/p&gt;&lt;p&gt;UPDATE: Many thanks to Haarg in #webgui for the clue stick beating! Turns out I was getting too big for my britches and trying to use rebase where a good ol' fashioned merge did the trick nicely. So here's the new workflow:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Upstream releases a new version, tags it v7.7.22 (for example)&lt;/li&gt;&lt;li&gt;I pull the latest changes into my local copy of the upstream branch:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;git checkout webgui-7.7&lt;br /&gt;git pull origin&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I now checkout my local modifications branch:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;git checkout webgui-7.7-pin&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I then merge the new version tag into my local branch (git is smart enough to go find the "v7.7.22" tag on the webgui-7.7 branch):&lt;br /&gt;&lt;code&gt;&lt;br /&gt;git merge v7.7.22&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Resolve any conflicts that created (it happens), commit them (not necessary if the merge created no conflicts), and then push to my cloud remote:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;git push cloud&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;This time the push was a fast-forward merge and the cloud remote happily accepts it. Hooray!&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6560382540998172563-7779711427869270625?l=nerdyadventuresofwes.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nerdyadventuresofwes.blogspot.com/feeds/7779711427869270625/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/09/using-git-to-maintain-local-changes-to.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/7779711427869270625'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6560382540998172563/posts/default/7779711427869270625'/><link rel='alternate' type='text/html' href='http://nerdyadventuresofwes.blogspot.com/2009/09/using-git-to-maintain-local-changes-to.html' title='(How not to) Use git to maintain local changes to an upstream codebase'/><author><name>Wes</name><uri>http://www.blogger.com/profile/02459666409663099544</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='31' src='http://4.bp.blogspot.com/_pKVg3t3iAhs/SU_luKMWFLI/AAAAAAAAAtQ/HyEb5FetBQY/S220/Me+at+Winter+Park.jpg'/></author><thr:total>1</thr:total></entry></feed>
