<?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-7615241590176793465</id><updated>2012-01-20T01:22:33.175+13:00</updated><category term='apache'/><category term='nzoss'/><category term='postgres'/><category term='optimisation'/><category term='sysadmin'/><category term='web'/><category term='security'/><category term='programming'/><category term='gerrit'/><category term='wine'/><category term='conference'/><category term='django'/><category term='rt'/><category term='firefox'/><category term='mutt'/><category term='privoxy'/><category term='gearman'/><category term='git'/><category term='python'/><category term='csp'/><category term='nginx'/><category term='ssl'/><category term='openwrt'/><category term='debian'/><category term='catalyst'/><category term='mozilla'/><category term='mahara'/><category term='launchpad'/><category term='ubuntu'/><category term='raid'/><category term='browserid'/><category term='plupload'/><category term='database'/><category term='backup'/><category term='gstreamer'/><category term='gargoyle'/><title type='text'>Feeding the Cloud</title><subtitle type='html'>Sharing technical tips and tricks, as well as things I figured out the hard way.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/-/mahara'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/search/label/mahara'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-2244704455594649203</id><published>2011-05-30T18:30:00.002+12:00</published><updated>2011-05-30T18:30:00.497+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='launchpad'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='gerrit'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Integrating Launchpad and Gerrit Code Review</title><content type='html'>While talking about how &lt;a href="http://mahara.org/"&gt;Mahara&lt;/a&gt; uses &lt;a href="http://feeding.cloud.geek.nz/2011/04/code-reviews-with-gerrit-and-gitorious.html"&gt;Gerrit and Gitorious for code reviews&lt;/a&gt;, I forgot to include details on how to &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-gerrit.html#commentlink"&gt;link bugs&lt;/a&gt; in the commit logs to our bug tracker (&lt;a href="https://bugs.launchpad.net/mahara"&gt;Launchpad&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Links to Launchpad in the Gerrit review UI&lt;/h3&gt;Turning the bug numbers in git commit messages to links was just a matter of adding this to our &lt;tt&gt;etc/gerrit.config&lt;/tt&gt; file.&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;[commentlink "launchpad"]&lt;br /&gt;  match = "([Bb]ug\\s+#?)(\\d+)"&lt;br /&gt;  link = https://bugs.launchpad.net/mahara/+bug/$2&lt;/pre&gt;&lt;/blockquote&gt;All mentions of a bug number like "(bug #1234)" or "bug 5678" in a commit message or Gerrit review will be converted into links to the appropriate page on Launchpad.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Updating Launchpad bugs after Gerrit merges&lt;/h3&gt;Linking the original bug reports in Launchpad to committed code (and its review in Gerrit) was a little more complicated. It makes use of the &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-hooks.html"&gt;hooks&lt;/a&gt; available in Gerrit.&lt;br /&gt;&lt;br /&gt;The first step was to create a &lt;a href="https://launchpad.net/+login"&gt;new Launchpad account&lt;/a&gt; for these automated updates and to confirm the email address it will be sending emails from. In our case, the account is &lt;a href="https://launchpad.net/~dev-mahara"&gt;MaharaBot&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Then, we wrote a &lt;a href="http://gitorious.org/mahara/mahara-scripts/blobs/master/change-merged"&gt;custom hook script&lt;/a&gt; (inspired by &lt;a href="https://github.com/hobbs/jirret"&gt;one for JIRA&lt;/a&gt;) which we placed in &lt;tt&gt;~/mahara_reviews/etc/gerrit.config&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;For an example of what it sends to the bug tracker, see &lt;a href="https://bugs.launchpad.net/mahara/+bug/788457"&gt;bug 788457&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2244704455594649203?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/2244704455594649203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2244704455594649203' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/2244704455594649203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/2244704455594649203'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2011/05/integrating-launchpad-and-gerrit-code.html' title='Integrating Launchpad and Gerrit Code Review'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-7684808770197039262</id><published>2011-04-17T17:00:00.009+12:00</published><updated>2011-05-30T16:06:12.641+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='gerrit'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Code reviews with Gerrit and Gitorious</title><content type='html'>The &lt;a href="http://mahara.org/"&gt;Mahara&lt;/a&gt; &lt;a href="https://launchpad.net/mahara"&gt;project&lt;/a&gt; has just moved to mandatory code reviews for every commit that gets applied to core code.&lt;br /&gt;&lt;br /&gt;Here is a description of how &lt;a href="http://code.google.com/p/gerrit"&gt;Gerrit Code Review&lt;/a&gt;, the peer-review system used by &lt;a href="http://www.android.com"&gt;Android&lt;/a&gt;, was retrofitted into our existing &lt;a href="http://www.git-scm.com/"&gt;git&lt;/a&gt; repository on &lt;a href="http://www.gitorious.org/"&gt;Gitorious&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;(If you want to know more about Gerrit, listen to &lt;a href="http://www.twit.tv/floss118"&gt;this FLOSS Weekly interview&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Replacing existing Gitorious committers with a robot&lt;/h3&gt;The first thing to do was to log into Gitorious and remove commit rights from everyone in the &lt;a href="http://www.gitorious.org/mahara/mahara"&gt;main repository&lt;/a&gt;. Then I created a new &lt;a href="http://www.gitorious.org/~maharabot"&gt;maharabot&lt;/a&gt; account with a password-less SSH key (stored under &lt;tt&gt;/home/gerrit/.ssh/&lt;/tt&gt;) and made that new account the sole committer.&lt;br /&gt;&lt;br /&gt;This is to ensure that nobody pushes to the repository by mistake since all of these changes would be &lt;b&gt;overwritten by Gerrit&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Basic Gerrit installation&lt;/h3&gt;After going through the &lt;a href="https://gerrit.googlecode.com/svn/documentation/2.1.6/install.html"&gt;installation instructions&lt;/a&gt;, I logged into the Gerrit admin interface and created a new "mahara" &lt;a href="https://gerrit.googlecode.com/svn/documentation/2.1.6/project-setup.html"&gt;project&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I picked the "merge if necessary" submit action because "cherry-pick" would &lt;a href="http://groups.google.com/group/repo-discuss/browse_thread/thread/aa53fe63197183af"&gt;disable dependency tracking&lt;/a&gt; which is quite a handy feature.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Reverse proxy using Nginx&lt;/h3&gt;Since we wanted to offer Gerrit over HTTPS, I decided to run it behind an &lt;a href="http://www.nginx.org/"&gt;Nginx&lt;/a&gt; proxy. This is the Nginx configuration I ended up with:&lt;br /&gt;&lt;pre&gt;server {&lt;br /&gt;     listen 443;&lt;br /&gt;     server_name reviews.mahara.org;&lt;br /&gt;     add_header Strict-Transport-Security max-age=15768000;&lt;br /&gt;&lt;br /&gt;     ssl on;&lt;br /&gt;     ssl_certificate  /etc/ssl/certs/reviews.mahara.org.crt;&lt;br /&gt;     ssl_certificate_key  /etc/ssl/certs/reviews.mahara.org.pem;&lt;br /&gt;&lt;br /&gt;     ssl_session_timeout  5m;&lt;br /&gt;     ssl_session_cache shared:SSL:1m;&lt;br /&gt;&lt;br /&gt;     ssl_protocols  TLSv1;&lt;br /&gt;     ssl_ciphers  HIGH:!ADH;&lt;br /&gt;     ssl_prefer_server_ciphers   on;&lt;br /&gt;&lt;br /&gt;     location / {&lt;br /&gt;             proxy_pass   http://127.0.0.1:8081;&lt;br /&gt;             proxy_set_header X-Forwarded-For $remote_addr;&lt;br /&gt;             proxy_set_header Host $host;&lt;br /&gt;     }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Things to note:&lt;ul&gt;&lt;li&gt;An HTTP to HTTPS redirection is &lt;a href="https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Do_Not_Perform_Redirects_from_Non-TLS_Page_to_TLS_Login_Page"&gt;not provided&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/HTTP_Strict_Transport_Security"&gt;HSTS&lt;/a&gt; headers indicates to modern browsers that this URL should only be accessed via HTTPS.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Only strong SSL ciphers are enabled.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Before proxying requests to Gerrit, Nginx adds a few headers to identify the origin of the request. The &lt;tt&gt;Host&lt;/tt&gt; header in particular must be present otherwise &lt;a href="http://code.google.com/p/gerrit/issues/detail?id=905"&gt;built-in Gerrit redirections will fail&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Mail setup&lt;/h3&gt;To enable Gerrit to email reviewers and committers, I installed &lt;a href="http://www.postfix.org/"&gt;Postfix&lt;/a&gt; and used "reviews.mahara.org" as the "System mail name".&lt;br /&gt;&lt;br /&gt;Then I added the following to &lt;tt&gt;/home/gerrit/mahara_reviews/etc/gerrit.config&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;[user]&lt;br /&gt; email = "gerrit@reviews.mahara.org"&lt;/pre&gt;to fix the From address in outgoing emails.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Init script and cron&lt;/h3&gt;Following the &lt;a href="https://gerrit.googlecode.com/svn/documentation/2.1.6/install.html"&gt;installation instructions&lt;/a&gt;, I created these symlinks:&lt;br /&gt;&lt;pre&gt;ln -s /home/gerrit/mahara_reviews/bin/gerrit.sh /etc/init.d/gerrit&lt;br /&gt;cd /etc/rc2.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc3.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc4.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc5.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc0.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit K21gerrit&lt;br /&gt;cd /etc/rc1.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit K21gerrit&lt;br /&gt;cd /etc/rc6.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit K21gerrit&lt;/pre&gt;and put the following settings into &lt;tt&gt;/etc/default/gerritcodereview&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;GERRIT_SITE=/home/gerrit/mahara_reviews&lt;br /&gt;GERRIT_USER=gerrit&lt;br /&gt;GERRIT_WAR=/home/gerrit/gerrit.war&lt;/pre&gt;to automatically start and stop Gerrit.&lt;br /&gt;&lt;br /&gt;I also added a cron job in &lt;tt&gt;/etc/cron.d/gitcleanup&lt;/tt&gt; to ensure that the built-in git repository doesn't get bloated:&lt;pre&gt;MAILTO=&lt;i&gt;admin@example.com&lt;/i&gt;&lt;br /&gt;20 4 * * * gerrit GIT_DIR=/home/gerrit/mahara_reviews/git/mahara.git git gc --quiet&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Configuration enhancements&lt;/h3&gt;To allow images in change requests to be displayed inside the browser, I &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-gerrit.html#mimetype"&gt;marked them as safe&lt;/a&gt; in &lt;tt&gt;/home/gerrit/mahara_reviews/etc/gerrit.config&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;[mimetype "image/*"]&lt;br /&gt; safe = true&lt;/pre&gt;&lt;br /&gt;Another thing I did to enhance the review experience was to enable the gitweb repository browser:&lt;br /&gt;&lt;pre&gt;apt-get install gitweb&lt;/pre&gt;&lt;br /&gt;and to make checkouts faster by enabling anonymous Git access:&lt;br /&gt;&lt;pre&gt;[gerrit]&lt;br /&gt;  canonicalGitUrl = git://reviews.mahara.org/git/&lt;br /&gt;[download]&lt;br /&gt;  scheme = ssh&lt;br /&gt;  scheme = anon_http&lt;br /&gt;  scheme = anon_git&lt;/pre&gt;&lt;br /&gt;which requires that you have a git daemon running and listening on port 9418:&lt;br /&gt;&lt;pre&gt;apt-get install git-daemon-run&lt;br /&gt;ln -s /home/gerrit/mahara_reviews/git/mahara.git /var/cache/git/&lt;br /&gt;touch /home/gerrit/mahara_reviews/git/mahara.git/git-daemon-export-ok&lt;/pre&gt;&lt;br /&gt;Finally, I included the Mahara branding in the header and footer of each page by &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-headerfooter.html#toc0"&gt;providing valid XHTML fragments&lt;/a&gt; in &lt;tt&gt;/home/gerrit/mahara_reviews/etc/GerritSiteHeader.html&lt;/tt&gt; and &lt;tt&gt;GerritSiteFooter.html&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Initial import and replication&lt;/h3&gt;Once Gerrit was fully working, I performed the initial code import by using my administrator account to push the exiting Gitorious branches to the internal git repository:&lt;br /&gt;&lt;pre&gt;git remote add gerrit ssh://&lt;i&gt;username&lt;/i&gt;@reviews.mahara.org:29418/mahara&lt;br /&gt;git push gerrit 1.2_STABLE&lt;br /&gt;git push gerrit 1.3_STABLE&lt;br /&gt;git push gerrit master&lt;/pre&gt;Note that I had to &lt;b&gt;temporarily disable "Require Change IDs"&lt;/b&gt; in the project settings in order to import the old commits which didn't have these.&lt;br /&gt;&lt;br /&gt;To replicate the internal Gerrit repository back to Gitorious, I created a new &lt;tt&gt;/home/gerrit/mahara_reviews/etc/replication.config&lt;/tt&gt; file:&lt;br /&gt;&lt;pre&gt;[remote "gitorious"]&lt;br /&gt; url = gitorious.org:mahara/${name}.git&lt;br /&gt; push = +refs/heads/*:refs/heads/*&lt;br /&gt; push = +refs/tags/*:refs/tags/*&lt;/pre&gt;(The ${name} variable is &lt;a href="https://code.google.com/p/gerrit/issues/detail?id=896"&gt;required&lt;/a&gt; even when you have a single project.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Contributor instructions&lt;/h3&gt;This is how developers can get a working checkout of our code now:&lt;br /&gt;&lt;pre&gt;git clone git://gitorious.org/mahara/mahara.git&lt;br /&gt;cd mahara&lt;br /&gt;git remote add gerrit ssh://&lt;i&gt;username&lt;/i&gt;@reviews.mahara.org:29418/mahara&lt;br /&gt;git fetch gerrit&lt;br /&gt;scp -p -P 29418 reviews.mahara.org:hooks/commit-msg .git/hooks/&lt;/pre&gt;and this is how they can submit local changes to Gerrit:&lt;pre&gt;git push gerrit HEAD:refs/for/master&lt;/pre&gt;&lt;br /&gt;Anybody can submit change requests or &lt;a href="https://reviews.mahara.org"&gt;comment on them&lt;/a&gt; but make sure you &lt;b&gt;do not have the Cookie Pie&lt;/b&gt; Firefox extension installed or you will be &lt;a href="https://code.google.com/p/gerrit/issues/detail?id=887"&gt;unable to log into Gerrit&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-7684808770197039262?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/7684808770197039262/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=7684808770197039262' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/7684808770197039262'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/7684808770197039262'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2011/04/code-reviews-with-gerrit-and-gitorious.html' title='Code reviews with Gerrit and Gitorious'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-2844285854332126579</id><published>2010-10-11T14:00:00.002+13:00</published><updated>2010-10-11T14:12:15.261+13:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Taking the max/min of two columns in PostgreSQL</title><content type='html'>As part of a database &lt;a href="http://www.postgresql.org/docs/9.0/static/sql-createview.html"&gt;view&lt;/a&gt;, I found myself wanting to get &lt;a href="http://www.postgresql.org/"&gt;Postgres&lt;/a&gt; to display values from one of two columns, whichever was the largest.&lt;br /&gt;&lt;br /&gt;My first attempt was:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;SELECT id, &lt;b&gt;MAX&lt;/b&gt;(column1, column2) FROM table1;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;which of course didn't work because &lt;tt&gt;MAX()&lt;/tt&gt; is an &lt;a href="http://www.postgresql.org/docs/9.0/static/functions-aggregate.html"&gt;aggregate function&lt;/a&gt; which only takes a single parameter.&lt;br /&gt;&lt;br /&gt;What I was looking for instead, is something that was introduced in &lt;a href="http://www.postgresql.org/docs/9.0/static/release-8-1.html#AEN109656"&gt;8.1&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;SELECT id, &lt;b&gt;GREATEST&lt;/b&gt;(column1, column2) FROM table1;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;where &lt;tt&gt;GREATEST()&lt;/tt&gt; (and its opposite: &lt;tt&gt;LEAST()&lt;/tt&gt;) is a &lt;a href="http://www.postgresql.org/docs/9.0/static/functions-conditional.html"&gt;conditional expression&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2844285854332126579?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/2844285854332126579/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2844285854332126579' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/2844285854332126579'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/2844285854332126579'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2010/10/taking-maxmin-of-two-columns-in.html' title='Taking the max/min of two columns in PostgreSQL'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-3107957576002454168</id><published>2010-07-20T23:30:00.002+12:00</published><updated>2010-07-21T08:41:15.293+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Cherry-picking a range of git commits</title><content type='html'>The &lt;tt&gt;cherry-pick&lt;/tt&gt; command in &lt;a href="http://www.git-scm.com/"&gt;git&lt;/a&gt; allows you to copy commits from one branch to another, one commit at a time. In order to copy more than one commit at once, you need a different approach.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Cherry-picking a single commit&lt;/h2&gt;Say we have the following repository composed of three branches (&lt;tt&gt;master&lt;/tt&gt;, &lt;tt&gt;feature1&lt;/tt&gt; and &lt;tt&gt;stable&lt;/tt&gt;):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git tree --all&lt;br /&gt;* d9484311 (HEAD, master) Delete test file&lt;br /&gt;* 4d4a0da8 Add a test file&lt;br /&gt;| * 5753515c (stable) Add a license&lt;br /&gt;| * 4b95278e Add readme file&lt;br /&gt;|/&lt;br /&gt;| * a37658bd (feature1) Add fourth file&lt;br /&gt;| * a7785c10 Add lines to 3rd file&lt;br /&gt;| * 7f545188 Add third file&lt;br /&gt;| * 2bca593b Add line to second file&lt;br /&gt;| * 0c13e436 Add second file&lt;br /&gt;|/&lt;br /&gt;* d3199755 Add a line&lt;br /&gt;* b58d925c Initial commit&lt;/pre&gt;&lt;br /&gt;The "git tree" command is an alias I defined in my &lt;tt&gt;~/.gitconfig&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;[alias]&lt;br /&gt;tree = log --oneline --decorate --graph&lt;/pre&gt;&lt;br /&gt;To copy the license file (commit &lt;tt&gt;5753515c&lt;/tt&gt;) to the &lt;tt&gt;master&lt;/tt&gt; branch then we simply need to run:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git checkout master&lt;br /&gt;$ git cherry-pick 5753515c&lt;br /&gt;Finished one cherry-pick.&lt;br /&gt;[master 08ff7d4] Add a license&lt;br /&gt;1 files changed, 676 insertions(+), 0 deletions(-)&lt;br /&gt;create mode 100644 COPYING&lt;/pre&gt;&lt;br /&gt;and the repository now looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git tree --all&lt;br /&gt;* 08ff7d4a4 &lt;b&gt;(HEAD, master) Add a license&lt;/b&gt;&lt;br /&gt;* d94843113 Delete test file&lt;br /&gt;* 4d4a0da88 Add a test file&lt;br /&gt;| * 5753515c (stable) Add a license&lt;br /&gt;| * 4b95278e Add readme file&lt;br /&gt;|/&lt;br /&gt;| * a37658bd (feature1) Add fourth file&lt;br /&gt;| * a7785c10 Add lines to 3rd file&lt;br /&gt;| * 7f545188 Add third file&lt;br /&gt;| * 2bca593b Add line to second file&lt;br /&gt;| * 0c13e436 Add second file&lt;br /&gt;|/&lt;br /&gt;* d3199755 Add a line&lt;br /&gt;* b58d925c Initial commit&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Cherry-picking a range of commits&lt;br /&gt;&lt;/h2&gt;In order to only take the third file (commits &lt;tt&gt;a7785c10&lt;/tt&gt; and &lt;tt&gt;7f545188&lt;/tt&gt;) from the &lt;tt&gt;feature1&lt;/tt&gt; branch and add it to the &lt;tt&gt;stable&lt;/tt&gt; branch, I could cherry-pick each commit separately, but there is a faster way if you need to cherry-pick a large range of commits.&lt;br /&gt;&lt;br /&gt;First of all, let's create a new branch which ends on the last commit we want to cherry-pick:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git branch tempbranch a7785c10&lt;br /&gt;$ git tree --all&lt;br /&gt;* 08ff7d4a (HEAD, master) Add a license&lt;br /&gt;* d9484311 Delete test file&lt;br /&gt;* 4d4a0da8 Add a test file&lt;br /&gt;| * 5753515c (stable) Add a license&lt;br /&gt;| * 4b95278e Add readme file&lt;br /&gt;|/&lt;br /&gt;| * a37658bd (feature1) Add fourth file&lt;br /&gt;| * a7785c10 &lt;b&gt;(tempbranch)&lt;/b&gt; Add lines to 3rd file&lt;br /&gt;| * 7f545188 Add third file&lt;br /&gt;| * 2bca593b Add line to second file&lt;br /&gt;| * 0c13e436 Add second file&lt;br /&gt;|/&lt;br /&gt;* d3199755 Add a line&lt;br /&gt;* b58d925c Initial commit&lt;/pre&gt;&lt;br /&gt;Now we'll rebase that temporary branch on top of the &lt;tt&gt;stable&lt;/tt&gt; branch:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git rebase --onto stable 7f545188^ tempbranch&lt;br /&gt;First, rewinding head to replay your work on top of it...&lt;br /&gt;Applying: Add third file&lt;br /&gt;Applying: Add lines to 3rd file&lt;br /&gt;$ git tree --all&lt;br /&gt;* ec488677 &lt;b&gt;(HEAD, tempbranch) Add lines to 3rd file&lt;/b&gt;&lt;br /&gt;* a85e5281 &lt;b&gt;Add third file&lt;/b&gt;&lt;br /&gt;* 5753515c (stable) Add a license&lt;br /&gt;* 4b95278e Add readme file&lt;br /&gt;| * 08ff7d4a (master) Add a license&lt;br /&gt;| * d9484311 Delete test file&lt;br /&gt;| * 4d4a0da8 Add a test file&lt;br /&gt;|/&lt;br /&gt;| * a37658bd (feature1) Add fourth file&lt;br /&gt;| * a7785c10 Add lines to 3rd file&lt;br /&gt;| * 7f545188 Add third file&lt;br /&gt;| * 2bca593b Add line to second file&lt;br /&gt;| * 0c13e436 Add second file&lt;br /&gt;|/&lt;br /&gt;* d3199755 Add a line&lt;br /&gt;* b58d925c Initial commit&lt;/pre&gt;&lt;br /&gt;All that's left to do is to make stable point to the top commit of &lt;tt&gt;tempbranch&lt;/tt&gt; and delete the old branch:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git checkout stable&lt;br /&gt;Switched to branch 'stable'&lt;br /&gt;$ git reset --hard tempbranch&lt;br /&gt;HEAD is now at ec48867 Add lines to 3rd file&lt;br /&gt;$ git tree --all&lt;br /&gt;* ec488677 (HEAD, tempbranch, &lt;b&gt;stable&lt;/b&gt;) Add lines to 3rd file&lt;br /&gt;* a85e5281 Add third file&lt;br /&gt;* 5753515c Add a license&lt;br /&gt;* 4b95278e Add readme file&lt;br /&gt;| * 08ff7d4a (master) Add a license&lt;br /&gt;| * d9484311 Delete test file&lt;br /&gt;| * 4d4a0da8 Add a test file&lt;br /&gt;|/&lt;br /&gt;| * a37658bd (feature1) Add fourth file&lt;br /&gt;| * a7785c10 Add lines to 3rd file&lt;br /&gt;| * 7f545188 Add third file&lt;br /&gt;| * 2bca593b Add line to second file&lt;br /&gt;| * 0c13e436 Add second file&lt;br /&gt;|/&lt;br /&gt;* d3199755 Add a line&lt;br /&gt;* b58d925c Initial commit&lt;br /&gt;$ git branch -d tempbranch&lt;br /&gt;Deleted branch tempbranch (was ec48867).&lt;/pre&gt;&lt;br /&gt;It would be nice to be able to do it without having to use a temporary branch, but it still beats cherry-picking everything manually.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Another approach&lt;/h2&gt;Another way to achieve this is to use the &lt;tt&gt;format-patch&lt;/tt&gt; command to output patches for the commits you are interested in copying to another branch and then using the &lt;tt&gt;am&lt;/tt&gt; command to apply them all to the target branch:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ git format-patch 7f545188^..a7785c10&lt;br /&gt;0001-Add-third-file.patch&lt;br /&gt;0002-Add-lines-to-3rd-file.patch&lt;br /&gt;$ git am *.patch&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Update: looking forward to git 1.7.2&lt;/h2&gt;According to a few people who were nice to point this out in a comment, version 1.7.2 of git, which is going to be released soon, will have support for this in &lt;tt&gt;cherry-pick&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;git cherry-pick 7f545188^..a7785c10&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-3107957576002454168?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/3107957576002454168/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=3107957576002454168' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/3107957576002454168'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/3107957576002454168'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2010/07/cherry-picking-range-of-git-commits.html' title='Cherry-picking a range of git commits'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-4963006486593641199</id><published>2010-06-14T17:20:00.000+12:00</published><updated>2010-06-14T17:21:40.607+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Handling email-based events safely</title><content type='html'>Mahara.org users have recently witnessed the unfortunate effects of a bug in the Mahara event handling: the Mahara cron job got stuck in an email loop and kept on sending the &lt;a href="http://mahara.org/interaction/forum/topic.php?id=1882"&gt;same forum post&lt;/a&gt; over and over again.&lt;br /&gt;&lt;br /&gt;Here is what the affected code used to look like (edited for clarity):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;db_begin();&lt;br /&gt;$activities = get_records('activity_queue');&lt;br /&gt;foreach ($activities as $activity) {&lt;br /&gt;  handle_activity($activity);&lt;br /&gt;}&lt;br /&gt;delete_records('activity_queue');&lt;br /&gt;db_commit();&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;One of the problems with this code was that if the &lt;tt&gt;handle_activity()&lt;/tt&gt; function threw an exception, it would interfere with the processing of the entire queue. So &lt;a href="http://gitorious.org/mahara/mahara/commit/823ab3168aed5ae0e4de27139d56b50c0ab0787c"&gt;it got fixed&lt;/a&gt; in the following way:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;db_begin();&lt;br /&gt;$activities = get_records('activity_queue');&lt;br /&gt;foreach ($activities as $activity) {&lt;br /&gt;  &lt;b&gt;try {&lt;/b&gt;&lt;br /&gt;      handle_activity($activity);&lt;br /&gt;  &lt;b&gt;}&lt;br /&gt;  catch (MaharaException $e) {&lt;br /&gt;      log_debug($e-&gt;getMessage());&lt;br /&gt;  }&lt;/b&gt;&lt;br /&gt;}&lt;br /&gt;delete_records('activity_queue');&lt;br /&gt;db_commit();&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Much better. However, there was still a problem with the code: the whole queue processing is contained within a transaction. This means that should any of the SQL statements fail at any point, the exception would be caught but the SQL statements would all be rolled back at the end by the database.&lt;br /&gt;&lt;br /&gt;Now the idea of a transaction is good: we want activity handling to either succeed entirely or be rolled back. But the fact that some activities cannot be handled should not interfere with other activities. So this has &lt;a href="http://gitorious.org/mahara/mahara/commit/d08395ac1308727dd45396083f454c47605c0604"&gt;been fixed&lt;/a&gt; by moving the transaction to the inside of the loop:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$activities = get_records('activity_queue');&lt;br /&gt;foreach ($activities as $activity) {&lt;br /&gt;  &lt;b&gt;db_begin();&lt;/b&gt;&lt;br /&gt;  try {&lt;br /&gt;      handle_activity($activity);&lt;br /&gt;  }&lt;br /&gt;  catch (MaharaException $e) {&lt;br /&gt;      log_debug($e-&gt;getMessage());&lt;br /&gt;  }&lt;br /&gt;  &lt;b&gt;db_commit();&lt;/b&gt;&lt;br /&gt;}&lt;br /&gt;delete_records('activity_queue');&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So individual activites are allowed to fail and get rolled back, but they will not affect other activites. But there was still one remaining problem: what if we encounter an error we cannot catch? For example, what would happen if PHP were to segfault or run out of memory while the activity queue is being processed?&lt;br /&gt;&lt;br /&gt;Well, in that case, it turns out that Mahara will never reach the &lt;tt&gt;delete_records()&lt;/tt&gt; call and the activity queue will never be cleared. Which means that on the next cron run, all of the activities will be handled again, even the ones that were successfully handled already (i.e. emails will be sent over and over again).&lt;br /&gt;&lt;br /&gt;The way we &lt;a href="http://gitorious.org/mahara/mahara/commit/d736fd6c0c99cb998af56979b12ceab60ed101ee"&gt;fixed this problem&lt;/a&gt; was by moving the &lt;tt&gt;delete_records()&lt;/tt&gt; from the end of the function to the beginning of the loop:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$activities = get_records('activity_queue');&lt;br /&gt;foreach ($activities as $activity) {&lt;br /&gt;  &lt;b&gt;delete_records('activity_queue', 'id', $activity-&gt;id);&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;  db_begin();&lt;br /&gt;  try {&lt;br /&gt;      handle_activity($activity);&lt;br /&gt;  }&lt;br /&gt;  catch (MaharaException $e) {&lt;br /&gt;      log_debug($e-&gt;getMessage());&lt;br /&gt;  }&lt;br /&gt;  db_commit();&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Each activity is removed from the queue &lt;b&gt;before&lt;/b&gt; it is processed.&lt;br /&gt;&lt;br /&gt;Unfortunately, there is a downside to this modification: should an activity handler fail for whatever reason, no further attempts will be made. This means that some notifications could be lost if an unexpected error occurs.&lt;br /&gt;&lt;br /&gt;However, given that some of the activity handlers send emails out into the world and that it is not possible to "un-send" them, this is the only way we can guarantee that no duplicate emails will be sent. Of course, if you notice that certain notifications are lost because of a bug in Mahara, &lt;a href="https://bugs.launchpad.net/mahara/+filebug"&gt;let us know&lt;/a&gt; and we'll fix it!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-4963006486593641199?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/4963006486593641199/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=4963006486593641199' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4963006486593641199'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4963006486593641199'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2010/06/handling-email-based-events-safely.html' title='Handling email-based events safely'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-8489512189025315844</id><published>2010-03-31T13:36:00.001+13:00</published><updated>2010-03-31T13:38:39.986+13:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>"Abusing" git storage</title><content type='html'>A &lt;a href="http://git-scm.com/"&gt;git&lt;/a&gt; repository is primarily intended to store multiple branches of a single program or component. The underlying system however is much more flexible. Here are two ways to add files which are related to the project but outside the normal history.&lt;br /&gt;&lt;h2&gt;Storing a single file all by itself&lt;/h2&gt;This &lt;a href="http://andyjeffries.co.uk/articles/25-tips-for-intermediate-git-users"&gt;intermediate trick&lt;/a&gt; allows you to store a single file into a git repository without the file being in any of the branches.&lt;br /&gt;&lt;br /&gt;First of all, let's create a new file:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;echo "&lt;b&gt;Hi&lt;/b&gt;" &amp;gt; &lt;i&gt;message.txt&lt;/i&gt;&lt;/code&gt;&lt;/blockquote&gt;and store it in git:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git hash-object -w &lt;i&gt;message.txt&lt;/i&gt;&lt;br /&gt;&lt;b&gt;b14df6442ea5a1b382985a6549b85d435376c351&lt;/b&gt;&lt;/code&gt;&lt;/blockquote&gt;At this stage, the file is stored within the git repository but there is no other way to get to it other than using the hash:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git cat-file blob &lt;b&gt;b14df6442ea5a1b382985a6549b85d435376c351&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Hi&lt;/b&gt;&lt;/code&gt;&lt;/blockquote&gt;A good way to point to the stored contents is to use a tag:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git tag &lt;i&gt;message.txt&lt;/i&gt; &lt;b&gt;b14df6442ea5a1b382985a6549b85d435376c351&lt;/b&gt;&lt;/code&gt;&lt;/blockquote&gt;Now you can access your file this way:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git cat-file blob &lt;i&gt;message.txt&lt;/i&gt;&lt;br /&gt;&lt;b&gt;Hi&lt;/b&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;h2&gt;Creating an empty branch&lt;/h2&gt;As seen in this &lt;a href="http://www.gitcasts.com/posts/empty-branches"&gt;git screencast&lt;/a&gt; or in the &lt;a href="http://book.git-scm.com/5_creating_new_empty_branches.html"&gt;Git Community Book&lt;/a&gt;, you can create a branch without a parent or any initial contents:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git symbolic-ref HEAD refs/heads/&lt;span style="font-style: italic;"&gt;foo&lt;/span&gt;&lt;br /&gt;rm .git/index&lt;br /&gt;git clean -fdx&lt;/code&gt;&lt;/blockquote&gt;To finish it off, add what you were planning on storing there and commit it:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git add &lt;i&gt;myfile&lt;/i&gt;&lt;br /&gt;git commit -m 'Initial commit'&lt;br /&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_kmDRplXH_Ys/S7KYy4YDjfI/AAAAAAAAAEE/ve5KngW5VWU/s1600/Capture.png"&gt;&lt;img src="http://3.bp.blogspot.com/_kmDRplXH_Ys/S7KYy4YDjfI/AAAAAAAAAEE/ve5KngW5VWU/s400/Capture.png" style="cursor:pointer; cursor:hand;width: 400px; height: 79px;" border="0" alt=""id="BLOGGER_PHOTO_ID_5454590098593254898" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-8489512189025315844?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/8489512189025315844/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=8489512189025315844' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/8489512189025315844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/8489512189025315844'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2010/03/abusing-git-storage.html' title='&quot;Abusing&quot; git storage'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_kmDRplXH_Ys/S7KYy4YDjfI/AAAAAAAAAEE/ve5KngW5VWU/s72-c/Capture.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-4834839572126540007</id><published>2010-02-09T16:45:00.000+13:00</published><updated>2010-02-09T16:57:32.580+13:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Excluding files from git archive exports using gitattributes</title><content type='html'>&lt;a href="http://www.kernel.org/pub/software/scm/git/docs/git-archive.html"&gt;git archive&lt;/a&gt; provides an easy way of producing a tarball directly from a project's git branch.&lt;br /&gt;&lt;br /&gt;For example, this is what we use to build the &lt;a href="http://www.mahara.org"&gt;Mahara&lt;/a&gt; tarballs:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git archive --format=tar --prefix=mahara-${VERSION}/ ${RELEASETAG} | bzip2 -9 &gt; ${CURRENTDIR}/mahara-${RELEASE}.tar.bz2&lt;/code&gt;&lt;/blockquote&gt;If you do this however, you end up with the entire contents of the git branch, including potentially undesirable files like &lt;tt&gt;.gitignore&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;There is an easy, though not very well-documented, way of specifying files to exclude from such exports: &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/gitattributes.html"&gt;gitattributes&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is what the Mahara &lt;tt&gt;.gitattributes&lt;/tt&gt; file looks like:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;/test &lt;b&gt;export-ignore&lt;/b&gt;&lt;br /&gt;.gitattributes &lt;b&gt;export-ignore&lt;/b&gt;&lt;br /&gt;.gitignore &lt;b&gt;export-ignore&lt;/b&gt;&lt;/code&gt;&lt;/blockquote&gt;With this file in the root directory of our repository, tarballs we generate using &lt;tt&gt;git archive&lt;/tt&gt; no longer contain the &lt;a href="http://www.seleniumhq.org"&gt;selenium&lt;/a&gt; tests or the git config files.&lt;br /&gt;&lt;br /&gt;If you start playing with this feature however, make sure you &lt;b&gt;commit the &lt;tt&gt;.gitattributes&lt;/tt&gt; file to your repository before running &lt;tt&gt;git archive&lt;/tt&gt;&lt;/b&gt;. Otherwise the settings will not be picked up by &lt;tt&gt;git archive&lt;/tt&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-4834839572126540007?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/4834839572126540007/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=4834839572126540007' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4834839572126540007'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4834839572126540007'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2010/02/excluding-files-from-git-archive.html' title='Excluding files from git archive exports using gitattributes'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-8799540138753212173</id><published>2009-12-27T21:45:00.001+13:00</published><updated>2009-12-28T09:56:48.472+13:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Ignoring files in git repositories</title><content type='html'>According to the &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/gitignore.html"&gt;man page&lt;/a&gt;, there are three ways to exclude files from being tracked by &lt;a href="http://www.git-scm.org"&gt;git&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Shared list of files to ignore &lt;/h2&gt;The most well-known way of preventing files from being part of a git branch is to add such files in &lt;tt&gt;.gitignore&lt;/tt&gt;. (This is analogous to CVS' &lt;tt&gt;.cvsignore&lt;/tt&gt; files.)&lt;br /&gt;&lt;br /&gt;Here's an example:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;*.generated.html&lt;br /&gt;/config.php&lt;/pre&gt;&lt;/blockquote&gt;The above ignore list will prevent automatically generated HTML files from being committed by mistake to the repository. Because this is useful to all developers on the project, &lt;tt&gt;.gitignore&lt;/tt&gt; is a good place for this.&lt;br /&gt;&lt;br /&gt;The next line prevents the local configuration file from being tracked by git, something else that all developers will want to have.&lt;br /&gt;&lt;br /&gt;One thing to note here is the use of a leading slash character with &lt;tt&gt;config.php&lt;/tt&gt;. This is to specifically match the config file in the same directory as the &lt;tt&gt;.gitignore&lt;/tt&gt; file (in this case, the root directory of the repository) but no other. Without this slash, the following files would also be ignored by git:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;/app/config.php&lt;br /&gt;/plugins/address/config.php&lt;br /&gt;/module/config.php&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h2&gt;Local list (specific to one project)&lt;/h2&gt;For those custom files that you don't want version controlled but that others probably don't have or don't want to automatically ignore, git provides a second facility: &lt;tt&gt;.git/info/exclude&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;It works the same way as &lt;tt&gt;.gitignore&lt;/tt&gt; but be aware that this list is only stored locally and only applies to the repository in which it lives.&lt;br /&gt;&lt;br /&gt;(I can't think of a good example for when you'd want to use this one because I don't really use it. Feel free to leave a comment if you do use it though, I'm curious to know what others do with it.)&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Local list (common to all projects)&lt;/h2&gt;Should you wish to automatically ignore file patterns in all of your projects, you will need to use the third gitignore method: core.excludesfile&lt;br /&gt;&lt;br /&gt;Put this line in your &lt;tt&gt;~/.gitconfig&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;[core]&lt;br /&gt;excludesfile = /home/&lt;i&gt;username&lt;/i&gt;/.gitexcludes&lt;/pre&gt;&lt;/blockquote&gt;(you need to put the absolute path to your home directory, &lt;tt&gt;~/&lt;/tt&gt; will not work here unless you use git 1.6.6 or later)&lt;br /&gt;&lt;br /&gt;and then put the patterns to ignore in &lt;tt&gt;~/.gitexcludes&lt;/tt&gt;. For example, this will ignore the automatic &lt;a href="http://www.gnu.org/software/emacs/manual/html_node/emacs/Backup.html"&gt;backups made by emacs&lt;/a&gt; when you save a file:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;*~&lt;/pre&gt;&lt;/blockquote&gt;This is the ideal place to put anything that is generated by your development tools and that doesn't need to appear in your project repositories.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-8799540138753212173?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/8799540138753212173/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=8799540138753212173' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/8799540138753212173'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/8799540138753212173'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2009/12/ignoring-files-in-git-repositories.html' title='Ignoring files in git repositories'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-6435899765648327852</id><published>2009-10-09T18:30:00.004+13:00</published><updated>2010-06-20T12:28:55.699+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='optimisation'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Reducing website bandwidth usage</title><content type='html'>There are lots of &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/5369"&gt;excellent&lt;/a&gt; &lt;a href="http://code.google.com/speed/page-speed/"&gt;tools&lt;/a&gt; to help web developers optimise their websites.&lt;br /&gt;&lt;br /&gt;Here are two simple things you have no excuse for overlooking on your next project.&lt;br /&gt;&lt;h2&gt;HTML, XML, Javascript and CSS files&lt;/h2&gt;One of the easiest ways to speed up a website (often to a surprising degree) is to turn on compression of plaintext content through facilities like &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_deflate.html"&gt;mod_deflate&lt;/a&gt; or the &lt;a href="http://wiki.nginx.org/NginxHttpGzipModule"&gt;Gzip Module&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here's the Apache configuration file I normally use:&lt;br /&gt;&lt;pre&gt;&lt;ifmodule&gt;AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript text/xml application/x-javascript application/javascript&lt;br /&gt;BrowserMatch ^Mozilla/4 gzip-only-text/html&lt;br /&gt;BrowserMatch "MSIE 6" no-gzip dont-vary&lt;br /&gt;BrowserMatch ^Mozilla/4\.0[678] no-gzip&lt;/ifmodule&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Images&lt;/h2&gt;As far as images go, the following tools will reduce file sizes through &lt;a href="http://en.wikipedia.org/wiki/Lossless_compression"&gt;lossless compression&lt;/a&gt; (i.e. with no visual changes at all):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;&lt;a href="http://www.lcdf.org/gifsicle/"&gt;gifsicle&lt;/a&gt; -O2 -b &lt;i&gt;image.gif&lt;/i&gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;jpegoptim -p --strip-all &lt;i&gt;image.jpg&lt;/i&gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;optipng -o7 -q &lt;i&gt;image.png&lt;/i&gt;&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;(An alternative to &lt;a href="http://optipng.sourceforge.net/"&gt;optipng&lt;/a&gt; is &lt;a href="http://pmt.sourceforge.net/pngcrush/"&gt;pngcrush&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;Note that the &lt;tt&gt;--strip-all&lt;/tt&gt; argument to &lt;a href="http://www.kokkonen.net/tjko/projects.html"&gt;jpegoptim&lt;/a&gt; will remove any &lt;a href="http://en.wikipedia.org/wiki/Exif"&gt;EXIF&lt;/a&gt;/comments tags that may be present.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-6435899765648327852?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/6435899765648327852/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=6435899765648327852' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/6435899765648327852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/6435899765648327852'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2009/10/reducing-website-bandwidth-usage.html' title='Reducing website bandwidth usage'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-4650021723036570516</id><published>2009-07-29T23:52:00.000+12:00</published><updated>2009-07-29T23:52:39.035+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>3 ways to improve your source control history</title><content type='html'>A few weeks ago, Linus Torvalds wrote a &lt;a href="http://torvalds-family.blogspot.com/2009/06/happiness-is-warm-scm.html"&gt;blog post&lt;/a&gt; talking about how happy he was with the way that the last Linux merge window was going. Especially given how flexible distributed source control systems are and how long it takes before developers understand &lt;a href="http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html"&gt;how to use them properly&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Most Free Software projects collaborate with external contributors through the use of &lt;a href="http://feeding.cloud.geek.nz/2009/06/writing-perfect-patch.html"&gt;patches&lt;/a&gt; and with project members through &lt;b&gt;shared source control&lt;/b&gt; systems. Keeping a clean source control history in this case can:&lt;ul&gt;&lt;li&gt;ease maintenance of the codebase (especially when merging between multiple active branches) and&lt;/li&gt;&lt;li&gt;facilitate collaboration with other developers who need to quickly grasp what your changes are all about.&lt;/li&gt;&lt;/ul&gt;Here are three things I've put in practice in a few recent projects.&lt;br /&gt;&lt;h2&gt;Reduce the noise on the main branch&lt;/h2&gt;If you use your source control system in a traditional centralised way, make sure that the main branch has as little noise as possible:&lt;ul&gt;&lt;li&gt;use &lt;b&gt;feature branches&lt;/b&gt; when developing new functionality (i.e. use one branch per feature)&lt;/li&gt;&lt;li&gt;&lt;b&gt;rebase&lt;/b&gt; into a small number of commits instead of merging directly into the main branch (or use something like &lt;tt&gt;git cherry-pick -n&lt;/tt&gt; on your commits) &lt;/li&gt;&lt;/ul&gt;It will then be much easier to &lt;b&gt;port these changes across to a different version&lt;/b&gt; and to &lt;b&gt;revisit the changes several months later&lt;/b&gt;.&lt;br /&gt;&lt;h2&gt;Make each commit a logical unit&lt;/h2&gt;Ideally, the main branch should be &lt;b&gt;fully usable at any point in its history&lt;/b&gt;. For example, if a refactoring commit breaks an existing feature, it should also include any necessary fixes. &lt;i&gt;Progress&lt;/i&gt; and &lt;i&gt;fixup&lt;/i&gt; commits belong to private branches and should be cleaned up before the code is published on a public repository.&lt;br /&gt;&lt;br /&gt;A commit shouldn't be too small, but at the same time, it shouldn't be too large. Before pushing a large commit, ask yourself whether or not it could be broken down into &lt;b&gt;smaller or more meaningful units&lt;/b&gt;. Introducing a new feature through a number of smaller self-contained units often makes a branch's history much more readable.&lt;br /&gt;&lt;h2&gt;Write meaningful commit messages&lt;/h2&gt;A commit message will typically be seen along with its patch. Therefore the ideal commit message will include everything which is not immediatly obvious from the code changes:&lt;ul&gt;&lt;li&gt;A short &lt;b&gt;description of the new feature&lt;/b&gt; it introduces (or how to &lt;b&gt;reproduce the bug&lt;/b&gt; it fixes)&lt;/li&gt;&lt;li&gt;Links to &lt;b&gt;related commits&lt;/b&gt; (e.g. "Fix bug introduced in COMMIT_ID")&lt;/li&gt;&lt;li&gt;&lt;b&gt;Bug numbers&lt;/b&gt; related to this commit&lt;/li&gt;&lt;li&gt;The &lt;b&gt;main area&lt;/b&gt; of the code (or &lt;i&gt;subsystem&lt;/i&gt;) that this commit touches (as part of the short description, so that it's easily &lt;i&gt;grep&lt;/i&gt;able)&lt;/li&gt;&lt;/ul&gt;Here's an example of a good commit message from version 2.6.29 of the Linux kernel:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;commit 4869fdea77052c480c54b9e66a927ba036eab29e&lt;br /&gt;Author: Mikulas Patocka &lt;mpatocka@redhat.com&gt;&lt;br /&gt;Date:   Mon Jun 22 10:08:02 2009 +0100&lt;br /&gt;&lt;br /&gt;dm mpath: validate table argument count&lt;br /&gt;&lt;br /&gt;commit 0e0497c0c017664994819f4602dc07fd95896c52 upstream.&lt;br /&gt;&lt;br /&gt;The parser reads the argument count as a number but doesn't check that&lt;br /&gt;sufficient arguments are supplied. This command triggers the bug:&lt;br /&gt;&lt;br /&gt;dmsetup create mpath --table "0 `blockdev --getsize /dev/mapper/cr0`&lt;br /&gt; multipath 0 0 2 1 round-robin 1000 0 1 1 /dev/mapper/cr0&lt;br /&gt; round-robin 0 1 1 /dev/mapper/cr1 1000"&lt;br /&gt;kernel BUG at drivers/md/dm-mpath.c:530!&lt;br /&gt;&lt;/mpatocka@redhat.com&gt;&lt;/pre&gt;&lt;/blockquote&gt;Good commits and useful source control histories are great ways of documenting your code. If there are other conventions or practices you've found useful in your use of source control systems, please leave a comment!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-4650021723036570516?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/4650021723036570516/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=4650021723036570516' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4650021723036570516'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4650021723036570516'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2009/07/3-ways-to-improve-your-source-control.html' title='3 ways to improve your source control history'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-7731510958448121124</id><published>2009-06-30T23:00:00.001+12:00</published><updated>2009-07-01T00:10:54.468+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Writing the perfect patch</title><content type='html'>Other people &lt;a href="http://userweb.kernel.org/%7Eakpm/stuff/tpp.txt"&gt;have written&lt;/a&gt; and &lt;a href="http://cs.anu.edu.au/students/comp8440/lectures.php"&gt;talked (in Lecture 3)&lt;/a&gt; about writing the perfect patch for a Free Software project. The goal there is to &lt;b&gt;increase the likelihood that a patch will be accepted&lt;/b&gt; by the project developers.&lt;br /&gt;&lt;br /&gt;Integrating and testing patches takes time and so reducing that burden is essential when interacting with busy maintainers. Especially if they're volunteers.&lt;br /&gt;&lt;br /&gt;Here's what I try to keep in mind when preparing a patch.&lt;br /&gt;&lt;h2&gt;Use the right options to &lt;tt&gt;diff&lt;/tt&gt;&lt;/h2&gt;These two options should always be part of your call to the &lt;tt&gt;diff&lt;/tt&gt; command:&lt;ul&gt;&lt;li&gt;&lt;b&gt;-u&lt;/b&gt;: use the most common patch format, unidiff.&lt;/li&gt; &lt;li&gt;&lt;b&gt;-p&lt;/b&gt;: include the name of the function that's being changed.&lt;/li&gt;&lt;/ul&gt;and this one can be useful if the output seems unnecessarily large:&lt;ul&gt;&lt;li&gt;&lt;b&gt;-d&lt;/b&gt;: try hard to find a smaller set of changes.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Minimize the number of changes&lt;/h2&gt;You need to draw attention to the changes that you're proposing and remove all other potential distractions:&lt;ul&gt;&lt;li&gt;&lt;b&gt;Follow the coding style of the original file.&lt;/b&gt; Your changes must fully blend in or they are likely to be rejected.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Do not re-indent existing code.&lt;/b&gt; This will make it look like you modified every line.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Pay attention to whitespace changes.&lt;/b&gt; In particular: end-of-line characters, trailing spaces and tab-versus-space differences. Use the &lt;tt&gt;dos2unix&lt;/tt&gt; or &lt;tt&gt;unix2dos&lt;/tt&gt; commands if you need to.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Gratuitous refactoring of existing code.&lt;/b&gt; Unless the refactoring makes your change smaller or easier to understand, keep it for another patch.&lt;/li&gt;&lt;/ul&gt;Of course all of the above would be acceptable patches &lt;b&gt;on their own&lt;/b&gt;, just not combined with other types of changes.&lt;br /&gt;&lt;h2&gt;Only one logical change at a time&lt;/h2&gt;Patches often need to be broken up into a series of logical changes to avoid these two extremes:&lt;ul&gt;&lt;li&gt;the &lt;b&gt;gigantic patch&lt;/b&gt; which adds a number of features and fixes a couple of bugs but scares everybody&lt;/li&gt;&lt;li&gt;a &lt;b&gt;series of interdependent patches&lt;/b&gt; which all relate to the same change and must all be applied together&lt;/li&gt;&lt;/ul&gt;It's a bit of a balancing act, but a good rule of thumb is:&lt;ul&gt;&lt;li&gt;to have &lt;b&gt;one patch per feature or bug&lt;/b&gt; and&lt;/li&gt;&lt;li&gt;to try to find the smallest (yet meaningful) change which can be applied on its own.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;It's not just about the patch&lt;/h2&gt;Your patch can be really good, but the email (or the bug tracker update) announcing it should also contain:&lt;ul&gt;&lt;li&gt;a good &lt;b&gt;description&lt;/b&gt; of the problem it solves and how it solves it&lt;/li&gt;&lt;li&gt;the output of &lt;b&gt;&lt;tt&gt;diffstat&lt;/tt&gt;&lt;/b&gt; to give an idea of the size of the change&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-7731510958448121124?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/7731510958448121124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=7731510958448121124' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/7731510958448121124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/7731510958448121124'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2009/06/writing-perfect-patch.html' title='Writing the perfect patch'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-4625458287479958517</id><published>2009-05-30T17:00:00.004+12:00</published><updated>2009-05-30T17:09:29.737+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='sysadmin'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='nzoss'/><title type='text'>Troubleshooting Postgres Performance Problems</title><content type='html'>There are &lt;a href="http://momjian.us/main/writings/pgsql/hw_performance/index.html"&gt;great&lt;/a&gt; &lt;a href="http://www.varlena.com/GeneralBits/Tidbits/perf.html"&gt;resources&lt;/a&gt; to help tune &lt;a href="http://www.postgresql.org/"&gt;PostgresQL&lt;/a&gt; for performance, but say you've got a Postgres database which suddenly becomes very slow. How can you figure out what's going on?&lt;br /&gt;&lt;h2&gt;General system state&lt;/h2&gt;A slow database server will typically be running out of:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;cpu,&lt;/li&gt;&lt;li&gt;memory or&lt;/li&gt;&lt;li&gt;disk.&lt;/li&gt;&lt;/ul&gt;So the first thing you should do is to get an overview of the system state using common UNIX tools such as:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;top&lt;/tt&gt; (press &lt;b&gt;c&lt;/b&gt; to see the full command line): look at CPU and memory usage&lt;/li&gt;&lt;li&gt;&lt;tt&gt;iostat -x -m 5&lt;/tt&gt;: look at i/o wait and service time&lt;/li&gt;&lt;/ul&gt;If you notice that memory is exhausted, you could play with the following &lt;tt&gt;postgresql.conf&lt;/tt&gt; settings:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;shared_buffers&lt;br /&gt;work_mem&lt;br /&gt;effective_cache_size&lt;/pre&gt;&lt;/blockquote&gt;If you see a lot of I/O, then try adjusting these settings:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;checkpoint_segments&lt;br /&gt;autovacuum_naptime&lt;br /&gt;wal_buffers&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;h2&gt;Finding slow queries&lt;br /&gt;&lt;/h2&gt;On the database side, start by determining whether:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;there is one large query holding up all of the resources or&lt;/li&gt;&lt;li&gt;the number of queries is the main problem.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;To do this, make sure that the following setting is turned on in your &lt;tt&gt;postgresql.conf&lt;/tt&gt;:&lt;pre&gt;&lt;blockquote&gt;stats_command_string = on&lt;/blockquote&gt;&lt;/pre&gt;Then fire up &lt;tt&gt;psql &lt;i&gt;dbname&lt;/i&gt;&lt;/tt&gt; and look at the &lt;span style="font-weight: bold;"&gt;currently executing queries&lt;/span&gt;:&lt;pre&gt;&lt;blockquote&gt;SELECT usename, current_query, query_start&lt;br /&gt;FROM pg_stat_activity;&lt;/blockquote&gt;&lt;/pre&gt;(You can also add the &lt;tt&gt;procid&lt;/tt&gt; column to the query if you want to map a query to the process ID you see in &lt;tt&gt;top&lt;/tt&gt;.)&lt;br /&gt;&lt;br /&gt;If you can't get anything useful out of &lt;tt&gt;pg_stat_activity&lt;/tt&gt;, you may want to enable &lt;b&gt;slow query logging&lt;/b&gt; by adding this to your &lt;tt&gt;postgresql.conf&lt;/tt&gt;:&lt;pre&gt;&lt;blockquote&gt;log_min_duration_statement = 100&lt;/blockquote&gt;&lt;/pre&gt;(All queries which take more than 100 ms to execute will be printed to the main Postgres log file.)&lt;br /&gt;&lt;h2&gt;Examining a particular query&lt;/h2&gt;Once you have identified a slow query, you can time it by running this command before executing the query manually:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;\timing&lt;/pre&gt;&lt;/blockquote&gt;To get an idea of where Postgres spends its time when executing that query, look at the query plan:&lt;br /&gt;&lt;pre&gt;&lt;blockquote&gt;EXPLAIN &lt;span style="font-style: italic;"&gt;your_query&lt;/span&gt;;&lt;/blockquote&gt;&lt;/pre&gt;The numbers you see there are &lt;span style="font-weight: bold;"&gt;estimates&lt;/span&gt;. To run the query and get actual numbers, use this instead:&lt;pre&gt;&lt;blockquote&gt;EXPLAIN &lt;span style="font-weight: bold;"&gt;ANALYZE&lt;/span&gt; &lt;i style="font-style: italic;"&gt;your_query&lt;/i&gt;;&lt;/blockquote&gt;&lt;/pre&gt;If you still can't figure out why a certain query takes so long, have a look at the &lt;span style="font-weight: bold;"&gt;ratio between live and dead rows&lt;/span&gt; in the relevant tables:&lt;pre&gt;&lt;blockquote&gt;ANALYZE VERBOSE &lt;i&gt;tablename&lt;/i&gt;;&lt;/blockquote&gt;&lt;/pre&gt;Having too  many dead rows  is often a  sign of insufficient  vacuuming. You might want to &lt;b&gt;turn autovacuum on&lt;/b&gt; if it isn't already, or to make it a bit more aggressive by tweaking these settings:&lt;pre&gt;&lt;blockquote&gt;autovacuum_vacuum_scale_factor&lt;br /&gt;autovacuum_analyze_scale_factor&lt;br /&gt;&lt;/blockquote&gt;&lt;/pre&gt;&lt;br /&gt;Troubleshooting database performance is a bit of a black art, many thanks to Mark Kirkwood for sharing his Postgres wisdom with me.&lt;br /&gt;&lt;br /&gt;If there's anything else you have successfully used to find the cause of your performance woes, please feel free to leave a comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-4625458287479958517?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/4625458287479958517/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=4625458287479958517' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4625458287479958517'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/4625458287479958517'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2009/05/troubleshooting-postgres-performance.html' title='Troubleshooting Postgres Performance Problems'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7615241590176793465.post-2561925668604985632</id><published>2009-03-10T21:45:00.001+13:00</published><updated>2009-03-10T21:47:05.407+13:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='catalyst'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='mahara'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Handling security bugs in your Free Software project</title><content type='html'>If you are managing a Free Software project, you may eventually be confronted with a security vulnerability. Normally bugs in the Open Source world are discussed in a transparent way on public forums. In the case of security bugs however, there are benefits to &lt;b&gt;temporarily&lt;/b&gt; withholding these details from the public.&lt;br /&gt;&lt;br /&gt;Some people describe this approach as &lt;a href="http://en.wikipedia.org/wiki/Responsible_disclosure"&gt;responsible disclosure&lt;/a&gt;. It boils down to this:&lt;br /&gt;&lt;blockquote&gt;if the vulnerability is not publicly known, warn the vendors first and give them some time to fix it before making the details public.&lt;/blockquote&gt;If the vulnerability is already public knowledge, then you can focus on fixing it as soon as possible and maybe let reporters know about &lt;a href="http://oss-security.openwall.org/wiki/disclosure/researcher"&gt;a better way to report their findings&lt;/a&gt;.&lt;br /&gt;&lt;h2&gt;Sample Security Policy&lt;/h2&gt;Here's the procedure we now follow in the &lt;a href="http://www.mahara.org/"&gt;Mahara&lt;/a&gt; project for security bugs we found ourselves or which have been privately disclosed to us through &lt;a href="mailto:security@mahara.org"&gt;security@mahara.org&lt;/a&gt;:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Figure out the extent of the problem: which versions of Mahara are affected by this problem?&lt;/li&gt;&lt;li&gt;Fix the problem on all supported branches of the project &lt;b&gt;in a private source control repository&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;Share the vulnerability information with &lt;a href="http://oss-security.openwall.org/wiki/mailing-lists/vendor-sec"&gt;vendor-sec&lt;/a&gt; and request a &lt;a href="http://cve.mitre.org/cve/identifiers/index.html"&gt;CVE identifier&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Prepare release tarballs and packages.&lt;/li&gt;&lt;li&gt;Draft a security advisory for the &lt;a href="http://mahara.org/interaction/forum/view.php?id=43"&gt;Security forum&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Wait until the embargo date to push the release out.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Emails to vendor-sec should include:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;a description of the problem&lt;/li&gt;&lt;li&gt;any proof of concept code demonstrating the vulnerability&lt;br /&gt;&lt;/li&gt;&lt;li&gt;the list of affected versions&lt;/li&gt;&lt;li&gt;any patches you have prepared&lt;/li&gt;&lt;li&gt;a proposed embargo date&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Benefits of properly handling security bugs&lt;br /&gt;&lt;/h2&gt;First and foremost, it &lt;b&gt;protects end-users&lt;/b&gt; by giving them a chance to download and install fixed versions of your software before widespread exploitation of the security flaws.&lt;br /&gt;&lt;br /&gt;Secondly, it may &lt;b&gt;increase your project credibility&lt;/b&gt;. While you are admitting that your software has flaws, you are also demonstrating that your project is committed to dealing with the most serious ones promptly and in a responsible manner.&lt;br /&gt;&lt;br /&gt;Finally, sharing security flaws and having your fixes reviewed by a select group of experts may &lt;b&gt;reveal extra vulnerabilities you missed&lt;/b&gt; while preparing your patches. This gives you an opportunity to improve your fixes before they are released and to avoid having to issue yet another security advisory a few days later.&lt;br /&gt;&lt;br /&gt;Big thanks to &lt;a href="http://steffen-joeris.skolelinux.de/"&gt;Steffen Joeris&lt;/a&gt; for his help in shaping the Mahara policy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2561925668604985632?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://feeding.cloud.geek.nz/feeds/2561925668604985632/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2561925668604985632' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/2561925668604985632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7615241590176793465/posts/default/2561925668604985632'/><link rel='alternate' type='text/html' href='http://feeding.cloud.geek.nz/2009/03/handling-security-bugs-in-your-free.html' title='Handling security bugs in your Free Software project'/><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg'/></author><thr:total>2</thr:total></entry></feed>
