GearheadForHire, LLCInternet and Application Software Design Services2012-08-02T13:05:44-04:00Robert C. Wahlerrobert@gearheadforhire.comhttp://www.gearheadforhire.com/An introduction to RepoManager, batch management of multiple Git repositoriestag:www.gearheadforhire.com,2012-08-02:13439271442012-08-02T13:05:44-04:00<p>Presenting <a href="http://rubygems.org/gems/repo_manager">RepoManager</a>, a command line
interface (CLI) for batch management of multiple <a href="http://git-scm.com/">Git</a>
repositories. RepoManager is available under the MIT license. The Ruby source
is located at
<a href="https://github.com/robertwahler/repo_manager">github.com/robertwahler/repo_manager</a></p>
<iframe width="640" height="360" src="http://www.youtube.com/embed/8mEtsBZQtEs" frameborder="0" allowfullscreen></iframe>
<h3>Overview</h3>
<p>RepoManager is a wrapper for <a href="http://git-scm.com/">Git</a> , the distributed
version control system. RepoManager's wrapper functions allow a single Git
command to be executed across multiple git repositories.</p>
<p>For example, you have two git repositories named <em>repo1</em> and
<em>repo2</em> and you want to check the status of both working folders.</p>
<h4>without repoman</h4>
<pre><code>cd ~/workspace/delphi/repo1
git status
cd ~/workspace/delphi/repo2
git status
</code></pre>
<h4>with repoman</h4>
<pre><code>repo status
</code></pre>
<h4>suitable for</h4>
<ul>
<li>Maintenance and documentation of loosely connected source code repositories.</li>
<li>Synchronization or light weight mirroring of data across a network. That is a
job for rsync. Or is it? If you develop for multiple platforms across
multiple (virtual) machines rsync'ing may not be the best option. If you
already have everything tucked into git repositories, you can use a single
'repo pull' command to mirror all of your repositories to one location for
backup or reference.</li>
</ul>
<h4>not suitable for</h4>
<ul>
<li>Maintaining related source code repositories. There are suitable tools
for that including git's own 'git submodules',
<a href="https://github.com/apenwarr/git-subtree">git-subtree</a>, and
<a href="http://gitslave.sourceforge.net/">GitSlave</a>.</li>
</ul>
<h3>Getting started with RepoManager</h3>
<h4>installation</h4>
<p>The RepoManager gem is available on <a href="http://rubygems.org/gems/repo_manager">RubyGems.org</a></p>
<pre><code>gem install repo_manager
</code></pre>
<h4>help</h4>
<p>RepoManager's binary is named <em>repo</em></p>
<pre><code>repo --help
repo --tasks
repo help generate:init
</code></pre>
<h3>Example Usage: Using RepoManager to Backup and Synchronize PC Game Saves</h3>
<p>The remainder of this article will examine a single use case.</p>
<p>Use case: Backup and synchronization of PC save games folders to a
central repository (ie Drop Box folder) using Git. Game saves are
typically scattered across multiple folders and drives.</p>
<p>This example demonstrates the following features:</p>
<ul>
<li>Adding RepoManager user tasks, see <a href="https://github.com/robertwahler/repo_manager/tree/master/examples/pc_saved_game_backup/repo_manager/tasks">repo_manager/tasks/</a></li>
<li>Adding destructive git commands to the default whitelisted non-destructive git commands</li>
<li>Testing user tasks with Cucumber, see <a href="https://github.com/robertwahler/repo_manager/tree/master/examples/pc_saved_game_backup/repo_manager/features">repo_manager/features/</a></li>
<li>Relative paths (not absolute) in <a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/repo.conf">repo_manager/repo.conf</a> making the folder portable</li>
<li>Bash completion for repo names, works on Win32 using Cygwin or MSYS Bash</li>
</ul>
<p>The full source of this example is available at
<a href="https://github.com/robertwahler/repo_manager/tree/master/examples/">github.com/robertwahler/repo_manager/examples</a></p>
<h4>create configuration</h4>
<p>The following commands were used to create this example from scratch</p>
<pre><code>mkdir -p examples/pc_saved_game_backup && cd examples/pc_saved_game_backup
</code></pre>
<p>Create configuration structure with the built-in 'generate:init' task</p>
<blockquote><p>We are creating a local configuration. For a global configuration, you would
execute the init command in your home folder</p></blockquote>
<pre><code>repo generate:init repo_manager
</code></pre>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/repo_manager/screenshots/large/generate_init" target="_blank"><img src="/articles/ruby/repo_manager/screenshots/images/medium/generate_init.png" title="Create the initial configuration via generate:init" alt="Create the initial configuration via generate:init" /></a><p class="caption">Create the initial configuration via <em>generate:init</em></p></div>
</div>
<h4>add sample data</h4>
<p>Add a few example save game folders. These folders would normally be
scattered over the file system.</p>
<p>mines</p>
<pre><code>mkdir -p saved_games/mines/saves
# profile data will not be stored in the Git repo since it may differ from PC to PC
echo "# dummy profile data" > mines/my_profile.ini
echo "# dummy save" > saved_games/mines/saves/save1
echo "# dummy save" > saved_games/mines/saves/save2
</code></pre>
<p>hearts</p>
<pre><code>mkdir -p saved_games/hearts
echo "# dummy save" > saved_games/hearts/save1
echo "# dummy save" > saved_games/hearts/save2
</code></pre>
<h4>create remote folder</h4>
<p>This folder will act as a remote to hold bare Git repositories. These
repos will store backups of our game saves, normally, this folder would be
on a remote server, NAS, or Drop Box like service.</p>
<pre><code>mkdir remote
</code></pre>
<h4>create the specialized 'git init' task</h4>
<p>User tasks can be added directly to the repo_manager/tasks folder. This task
doesn't use any RepoManager specific features, instead, it calls git directly
via Thor's <em>run</em> command. Adding the script this way will keep this related
functionality with this specific RepoManager configuration. Run <em>repo -T</em> to
see a full list of built-in tasks as well as user defined tasks.</p>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/tasks/remote.rb">repo_manager/tasks/remote.rb</a></p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">require</span> 'fileutils'
<span class="Keyword">module</span> <span class="Entity">RepoManager</span>
<span class="Keyword">class</span> <span class="Entity">Generate<span class="Entity"> <span class="PunctuationSeparator"><</span> Thor</span></span>
<span class="Comment"> <span class="Comment">#</span> full path to the remote folder</span>
<span class="Variable">REMOTE</span> <span class="Keyword">=</span> <span class="Support">File</span><span class="PunctuationSeparator">.</span><span class="Entity">expand_path</span>('remote')
<span class="Comment"> <span class="Comment">#</span> Create, add, and commit the contents of the current working directory and</span>
<span class="Comment"> <span class="Comment">#</span> then push it to a predefined remote folder</span>
<span class="Comment"> <span class="Comment">#</span></span>
<span class="Comment"> <span class="Comment">#</span> @example From the repo working</span>
<span class="Comment"> <span class="Comment">#</span></span>
<span class="Comment"> <span class="Comment">#</span> cd ~/my_repo_name</span>
<span class="Comment"> <span class="Comment">#</span> repo generate:remote my_repo_name</span>
<span class="Comment"> <span class="Comment">#</span></span>
<span class="Comment"> <span class="Comment">#</span> @example Specify the path to the working folder</span>
<span class="Comment"> <span class="Comment">#</span></span>
<span class="Comment"> <span class="Comment">#</span> repo generate:remote my_repo_name --path=/path/to/my_repo_name</span>
method_option <span class="Constant"><span class="Constant">:</span>remote</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>type</span> <span class="PunctuationSeparator">=></span> <span class="Constant"><span class="Constant">:</span>string</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>desc</span> <span class="PunctuationSeparator">=></span> "remote folder or git host, defaults to '<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="StringVariable">REMOTE</span><span class="StringEmbeddedSource">}</span></span>'"
method_option <span class="Constant"><span class="Constant">:</span>path</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>type</span> <span class="PunctuationSeparator">=></span> <span class="Constant"><span class="Constant">:</span>string</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>desc</span> <span class="PunctuationSeparator">=></span> "path to working folder, defaults to CWD"
desc "remote REPO_NAME"<span class="PunctuationSeparator">,</span> "init a git repo in CWD and push to remote '<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="StringVariable">REMOTE</span><span class="StringEmbeddedSource">}</span></span>'"
<span class="Keyword">def</span> <span class="Entity">remote</span>(<span class="Variable">name</span>)
path <span class="Keyword">=</span> options[<span class="Constant"><span class="Constant">:</span>path</span>] <span class="Keyword">||</span> <span class="Support">FileUtils</span><span class="PunctuationSeparator">.</span><span class="Entity">pwd</span>
remote <span class="Keyword">=</span> options[<span class="Constant"><span class="Constant">:</span>remote</span>] <span class="Keyword">||</span> "<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="Support">File</span><span class="StringEmbeddedSource"><span class="PunctuationSeparator">.</span><span class="Entity">join</span></span><span class="StringEmbeddedSource">(</span><span class="StringVariable">REMOTE</span><span class="PunctuationSeparator">,</span> name <span class="Keyword">+</span> <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">'</span>.git<span class="StringEmbeddedSource">'</span></span><span class="StringEmbeddedSource">)</span><span class="StringEmbeddedSource">}</span></span>"
<span class="Support">Dir</span><span class="PunctuationSeparator">.</span><span class="Entity">chdir</span> path <span class="Keyword">do</span>
<span class="Entity">run</span>("git init")
<span class="Comment"> <span class="Comment">#</span> core config with windows in mind but works fine on POSIX</span>
<span class="Entity">run</span>("git config core.autocrlf false")
<span class="Entity">run</span>("git config core.filemode false")
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
<span class="Comment"> <span class="Comment">#</span> add everthing and commit</span>
<span class="Entity">run</span>("git add .")
<span class="Entity">run</span>("git commit --message <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="StringEmbeddedSource"><span class="Entity">shell_quote</span></span><span class="StringEmbeddedSource">(</span><span class="StringEmbeddedSource"><span class="StringEmbeddedSource">'</span>initial commit<span class="StringEmbeddedSource">'</span></span><span class="StringEmbeddedSource">)</span><span class="StringEmbeddedSource">}</span></span>")
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
<span class="Comment"> <span class="Comment">#</span> remove old origin first, if it exists</span>
<span class="Entity">run</span>("git remote add origin <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>remote<span class="StringEmbeddedSource">}</span></span>")
<span class="Entity">run</span>("git config branch.master.remote origin")
<span class="Entity">run</span>("git config branch.master.merge refs/heads/master")
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
<span class="Keyword">end</span>
<span class="Entity">run</span>("git clone --bare <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="StringEmbeddedSource"><span class="Entity">shell_quote</span></span><span class="StringEmbeddedSource">(</span>path<span class="StringEmbeddedSource">)</span><span class="StringEmbeddedSource">}</span></span> <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>remote<span class="StringEmbeddedSource">}</span></span>")
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
say "init done on '<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>name<span class="StringEmbeddedSource">}</span></span>'"<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>green</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<h5>add remotes</h5>
<p>In one step, we will initialize a new git repository with the working folder's
content and push to a new bare repository for backup.</p>
<blockquote><p>Normally, you don't need to specify the --path if you are already in the
working folder and the repo_manager can find its global config file. For this
example, we are using relative paths and will specify the working folder
on the command line via the '--path' option.</p></blockquote>
<pre><code>repo generate:remote mines --path=saved_games/mines/saves
repo generate:remote hearts --path=saved_games/hearts
</code></pre>
<h5>create the repo_manager asset configuration files</h5>
<pre><code>repo add:asset saved_games/mines/saves --name=mines --force
repo add:asset saved_games/hearts --force
</code></pre>
<h4>Get information on configured saved game repositories</h4>
<pre><code>repo list --short
repo status --unmodified DOTS
</code></pre>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/repo_manager/screenshots/large/list_short" target="_blank"><img src="/articles/ruby/repo_manager/screenshots/images/medium/list_short.png" title="Running repo list status unmodified repos" alt="Running repo list status unmodified repos" /></a><p class="caption">Running repo <em>list</em> <em>status</em> unmodified repos</p></div>
</div>
<h4>Create the specialized Update task</h4>
<p>The following user task will run repo add -A, repo commit, and repo push on all modified repos.</p>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/tasks/update.rb">repo_manager/tasks/update.rb</a></p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">module</span> <span class="Entity">RepoManager</span>
<span class="Keyword">class</span> <span class="Entity">Action<span class="Entity"> <span class="PunctuationSeparator"><</span> Thor</span></span>
namespace <span class="Constant"><span class="Constant">:</span>action</span>
<span class="Keyword">include</span> <span class="Support">Thor</span><span class="PunctuationSeparator">::</span><span class="Entity">Actions</span>
<span class="Keyword">include</span> <span class="Support">RepoManager</span><span class="PunctuationSeparator">::</span><span class="Entity">ThorHelper</span>
class_option <span class="Constant"><span class="Constant">:</span>force</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>type</span> <span class="PunctuationSeparator">=></span> <span class="Constant"><span class="Constant">:</span>boolean</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>desc</span> <span class="PunctuationSeparator">=></span> "Force overwrite and answer 'yes' to any prompts"
method_option <span class="Constant"><span class="Constant">:</span>repos</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>type</span> <span class="PunctuationSeparator">=></span> <span class="Constant"><span class="Constant">:</span>string</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>desc</span> <span class="PunctuationSeparator">=></span> "Restrict update to comma delimited list of repo names"<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>banner</span> <span class="PunctuationSeparator">=></span> "repo1,repo2"
method_option <span class="Constant"><span class="Constant">:</span>message</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>type</span> <span class="PunctuationSeparator">=></span> <span class="Constant"><span class="Constant">:</span>string</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>desc</span> <span class="PunctuationSeparator">=></span> "Override 'automatic commit' message"
method_option 'no-push'<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>type</span> <span class="PunctuationSeparator">=></span> <span class="Constant"><span class="Constant">:</span>boolean</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>default</span> <span class="PunctuationSeparator">=></span> <span class="Constant">false</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>desc</span> <span class="PunctuationSeparator">=></span> "Force overwrite of existing config file"
desc "update"<span class="PunctuationSeparator">,</span> "run repo add -A, repo commit, and repo push on all modified repos"
<span class="Keyword">def</span> <span class="Entity">update</span>
initial_filter <span class="Keyword">=</span> options[<span class="Constant"><span class="Constant">:</span>repos</span>] <span class="Keyword">?</span> "--repos=<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>options<span class="StringEmbeddedSource">[</span><span class="StringConstant"><span class="StringConstant">:</span>repos</span><span class="StringEmbeddedSource">]</span><span class="StringEmbeddedSource">}</span></span>" <span class="PunctuationSeparator">:</span> ""
output <span class="Keyword">=</span> <span class="Entity">run</span>("repo status --short --unmodified=HIDE --no-verbose --no-color <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>initial_filter<span class="StringEmbeddedSource">}</span></span>"<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>capture</span> <span class="PunctuationSeparator">=></span> <span class="Constant">true</span>)
<span class="Keyword">case</span> <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span>
<span class="Keyword">when</span> <span class="Constant">0</span>
say 'no changed repos'<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>green</span>
<span class="Keyword">else</span>
<span class="Keyword">unless</span> output
say "failed to successfully run 'repo status'"<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>red</span>
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span>
<span class="Keyword">end</span>
repos <span class="Keyword">=</span> []
output <span class="Keyword">=</span> output<span class="PunctuationSeparator">.</span><span class="Entity">split</span>("<span class="StringConstant">\n</span>")
<span class="Keyword">while</span> line <span class="Keyword">=</span> output<span class="PunctuationSeparator">.</span><span class="Entity">shift</span>
st<span class="PunctuationSeparator">,</span>repo <span class="Keyword">=</span> line<span class="PunctuationSeparator">.</span><span class="Entity">split</span>("<span class="StringConstant">\t</span>")
repos <span class="Keyword"><<</span> repo
<span class="Keyword">end</span>
filter <span class="Keyword">=</span> repos<span class="PunctuationSeparator">.</span><span class="Entity">join</span>(',')
<span class="Keyword">unless</span> options[<span class="Constant"><span class="Constant">:</span>force</span>]
say "Repo(s) '<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>filter<span class="StringEmbeddedSource">}</span></span>' have changed."
<span class="Keyword">unless</span> <span class="Entity">ask</span>("Add, commit and push them? (y/n)") <span class="Keyword">==</span> 'y'
say "aborting"
exit <span class="Constant">0</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
say "updating <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>filter<span class="StringEmbeddedSource">}</span></span>"
run "repo add -A --no-verbose --repos <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>filter<span class="StringEmbeddedSource">}</span></span>"
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
commit_message <span class="Keyword">=</span> options[<span class="Constant"><span class="Constant">:</span>message</span>] <span class="Keyword">||</span> "automatic commit @ <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="Support">Time</span><span class="StringEmbeddedSource"><span class="PunctuationSeparator">.</span><span class="Entity">now</span></span><span class="StringEmbeddedSource">}</span></span>"
run "repo commit --message=<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span><span class="StringEmbeddedSource"><span class="Entity">shell_quote</span></span><span class="StringEmbeddedSource">(</span>commit_message<span class="StringEmbeddedSource">)</span><span class="StringEmbeddedSource">}</span></span> --no-verbose --repos <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>filter<span class="StringEmbeddedSource">}</span></span>"
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
<span class="Keyword">unless</span> options['no-push']
run "repo push --no-verbose --repos <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>filter<span class="StringEmbeddedSource">}</span></span>"
exit <span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">if</span> (<span class="Variable"><span class="Variable">$</span>?</span><span class="PunctuationSeparator">.</span><span class="Entity">exitstatus</span> <span class="Keyword">></span> <span class="Constant">1</span>)
<span class="Keyword">end</span>
say "update finished"<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>green</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<h4>whitelist non-default Git commands</h4>
<p>Only a small subset of non-destructive git commands are enabled by default. We will
add the commands needed by our user task to the commands whitelist.</p>
<p>Edit repo.conf and add 'push, add, and commit' to the commands whitelist</p>
<div class="UltraViolet">
<pre class="dawn"> diff --git a/repo_manager/repo.conf b/repo_manager/repo.conf
index 3cc6dbe..226b8c0 100644
--- a/repo_manager/repo.conf
+++ b/repo_manager/repo.conf
@@ -36,6 +36,9 @@ commands:
- ls-files
- show
- status
+- push
+- add
+- commit
</pre>
</div>
<h4>using the new tasks</h4>
<p>To view all the available tasks</p>
<pre><code>repo --tasks
</code></pre>
<p>or just</p>
<pre><code>repo -T
</code></pre>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/repo_manager/screenshots/large/tasks" target="_blank"><img src="/articles/ruby/repo_manager/screenshots/images/medium/tasks.png" title="Listing RepoManager tasks includes user and built-in tasks" alt="Listing RepoManager tasks includes user and built-in tasks" /></a><p class="caption">Listing RepoManager tasks includes user and built-in tasks</p></div>
</div>
<h5>Using the <em>action:update</em> task to backup saved games.</h5>
<pre><code>repo action:update
</code></pre>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/repo_manager/screenshots/large/action_update" target="_blank"><img src="/articles/ruby/repo_manager/screenshots/images/medium/action_update.png" title="Example action:update usage with one new save game" alt="Example action:update usage with one new save game" /></a><p class="caption">Example <em>action:update</em> usage with one new save game</p></div>
</div>
<h5>Synchronizing saved games to another PC can be accomplished using Git's 'pull' command.</h5>
<p>verify working folders are clean, if they are not, either revert them or commit and push</p>
<pre><code>repo status
</code></pre>
<p>pull from remote to all configured repos</p>
<pre><code>repo pull
</code></pre>
<h3>Testing user tasks with Cucumber</h3>
<h4>Add a Gemfile for use by Bundler</h4>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/Gemfile">repo_manager/Gemfile</a></p>
<pre><code>source "http://rubygems.org"
gem "repo_manager"
gem "bundler", ">= 1.0.14"
gem "rspec", ">= 2.6.0"
gem "cucumber", "~> 1.0"
gem "aruba", "= 0.4.5"
gem "win32console", :platforms => [:mingw, :mswin]
</code></pre>
<h4>Install the dependencies</h4>
<pre><code>gem install bundler
cd repo_manager
bundle
</code></pre>
<h4>Add Cucumber features and support files</h4>
<blockquote><p>NOTE: This is an excerpt, see the file for the full listing of functional tests</p></blockquote>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/features/tasks/update.feature">repo_manager/features/tasks/update.feature</a></p>
<pre><code>@announce
Feature: Automatically commit and update multiple repos
Background: Test repositories and a valid config file
Given a repo in folder "test_path_1" with the following:
| filename | status | content |
| .gitignore | C | |
And a repo in folder "test_path_2" with the following:
| filename | status | content |
| .gitignore | C | |
And a file named "repo.conf" with:
"""
---
folders:
assets : repo/asset/configuration/files
"""
And the folder "repo/asset/configuration/files" with the following asset configurations:
| name | path |
| test1 | test_path_1 |
| test2 | test_path_2 |
Scenario: No uncommitted changes
When I run `repo action:update`
Then the output should contain:
"""
no changed repos
"""
...
</code></pre>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/features/support/steps.rb">repo_manager/features/support/steps.rb</a></p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">require</span> 'repo_manager/test/base_steps'
<span class="Keyword">require</span> 'repo_manager/test/asset_steps'
<span class="Keyword">require</span> 'repo_manager/test/repo_steps'
</pre>
</div>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/features/support/env.rb">repo_manager/features/support/env.rb</a></p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">require</span> 'repo_manager'
<span class="Keyword">require</span> 'aruba/cucumber'
<span class="Keyword">require</span> 'rspec/expectations'
<span class="Variable">Before</span> <span class="Keyword">do</span>
<span class="Variable"><span class="Variable">@</span>aruba_timeout_seconds</span> <span class="Keyword">=</span> <span class="Constant">10</span>
<span class="Keyword">end</span>
<span class="Variable">Before</span>('@slow_process') <span class="Keyword">do</span>
<span class="Variable"><span class="Variable">@</span>aruba_io_wait_seconds</span> <span class="Keyword">=</span> <span class="Constant">2</span>
<span class="Keyword">end</span>
</pre>
</div>
<p><a href="https://github.com/robertwahler/repo_manager/blob/master/examples/pc_saved_game_backup/repo_manager/features/support/aruba.rb">repo_manager/features/support/aruba.rb</a></p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">require</span> 'aruba/api'
<span class="Keyword">require</span> 'fileutils'
<span class="Keyword">module</span> <span class="Entity">Aruba</span>
<span class="Keyword">module</span> <span class="Entity">Api</span>
<span class="Comment"> <span class="Comment">#</span> override aruba avoid 'current_ruby' call and make sure</span>
<span class="Comment"> <span class="Comment">#</span> that binary run on Win32 without the binstubs</span>
<span class="Keyword">def</span> <span class="Entity">detect_ruby</span>(<span class="Variable">cmd</span>)
wrapper <span class="Keyword">=</span> <span class="Entity">which</span>('repo')
cmd <span class="Keyword">=</span> cmd<span class="PunctuationSeparator">.</span><span class="Entity">gsub</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">^repo</span><span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="PunctuationSeparator">,</span> "ruby -S <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>wrapper<span class="StringEmbeddedSource">}</span></span>") <span class="Keyword">if</span> wrapper
cmd
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<h4>Run the functional user tests</h4>
<pre><code>bundle exec cucumber
</code></pre>
<p>Session screenshots</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/repo_manager/screenshots/large/cucumber_run" target="_blank"><img src="/articles/ruby/repo_manager/screenshots/images/medium/cucumber_run.png" title="Testing users tasks with Cucumber" alt="Testing users tasks with Cucumber" /></a><p class="caption">Testing users tasks with Cucumber</p></div>
</div>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/repo_manager/screenshots/large/full_repoman_tree" target="_blank"><img src="/articles/ruby/repo_manager/screenshots/images/medium/full_repoman_tree.png" title="RepoManager file structure including user tasks" alt="RepoManager file structure including user tasks" /></a><p class="caption">RepoManager file structure including user tasks</p></div>
</div>
<h3>Bash completion</h3>
<p>Handy functions for use under Bash. These work fine on Win32 using
Git-Bash.</p>
<h4>CD command for working folders</h4>
<p>rpushd: repo pushd (push directory). Wrapper for 'pushd'.</p>
<h4>Completion for repo names</h4>
<p>rcd: repo cd (change directory). Wrapper for 'cd', allows for simple cd <em>repo
name</em> to the working folder on the filesystem referenced by the 'path'
configuration variable.</p>
<p>Source these functions in your .bashrc</p>
<pre><code>function rcd(){ cd "$(repo --match=ONE --no-color path $@)"; }
function rpushd(){ pushd "$(repo path --match=ONE --no-color $@)"; }
alias rpopd="popd"
# provide completion for repo names
function _repo_names()
{
local cur opts prev
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
opts=`repo list --list=name --no-color`
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _repo_names rcd rpushd repo
</code></pre>
<p>For more information, please consult the source: <a href="http://github.com/robertwahler/repo_manager">http://github.com/robertwahler/repo_manager</a>.</p>
Dynabix: An ActiveRecord Ruby gem for attribute serializationtag:www.gearheadforhire.com,2012-06-25:13406451752012-06-25T13:26:15-04:00<blockquote><p>Dy-na-bix, tasty serialization attribute accessors for ActiveRecord</p></blockquote>
<p>Presenting <a href="https://github.com/robertwahler/dynabix">Dynabix</a>. Dynabix is an
ActiveRecord 3.x <a href="http://rubygems.org/gems/dynabix">RubyGem</a> that facilitates
attribute serialization via dynamically created read/write accessors.</p>
<h3>Overview</h3>
<p>Data <a href="http://en.wikipedia.org/wiki/Serialization#Ruby">serialization</a> is a
technique that can be used to persist data to the database without changing the
schema when adding or removing attributes. A single text field can contain
multiple attributes. Serialization is useful for one-off situations like
voting polls or frequently changing on-line questionnaires. Dynabix uses
ActiveRecord's <a href="http://apidock.com/rails/ActiveRecord/AttributeMethods/Serialization/ClassMethods/serialize">serialize</a>
method under the hood.</p>
<h4>UPDATE 6/26/12</h4>
<p>ActiveRecord as of 3.2.1, as pointed out in the comments, has a very
similar native method
<a href="http://apidock.com/rails/ActiveRecord/Store">store</a>. Dynabix differs
from store by providing a declarative DSL for defining multiple stores
(Ruby 1.9+), has separate read/write accessors, and stores to the database
as HashWithIndifferentAccess. Unless you need one of these specific
features, using the native 'store' method is recommended.</p>
<h3>Source</h3>
<p>Dynabix's source is available under the MIT license here
<a href="https://github.com/robertwahler/dynabix">https://github.com/robertwahler/dynabix</a>.
The documentation is located on Rubydoc.info at
<a href="http://rubydoc.info/gems/dynabix/frames">http://rubydoc.info/gems/dynabix</a></p>
<h3>Usage</h3>
<p>Add a text column "metadata" to your model migration. This column will store
all the attribute values defined by Dynabix.</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">class</span> <span class="Entity">AddMetadataToThings<span class="Entity"> <span class="PunctuationSeparator"><</span> ActiveRecord::Migration</span></span>
<span class="Keyword">def</span> <span class="Entity">change</span>
add_column <span class="Constant"><span class="Constant">:</span>things</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>metadata</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>text</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>Add accessors to your model using the default column name ":metadata", specify
the attributes in a separate step.</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">class</span> <span class="Entity">Thing<span class="Entity"> <span class="PunctuationSeparator"><</span> ActiveRecord::Base</span></span>
has_metadata
<span class="Comment"> <span class="Comment">#</span> full accessors</span>
metadata_accessor <span class="Constant"><span class="Constant">:</span>breakfast_food</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>wheat_products</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>needs_milk</span>
<span class="Comment"> <span class="Comment">#</span> read-only accessor</span>
metadata_reader <span class="Constant"><span class="Constant">:</span>friends_with_spoons</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>Specifying attributes for full attribute accessors in one step</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">class</span> <span class="Entity">Thing<span class="Entity"> <span class="PunctuationSeparator"><</span> ActiveRecord::Base</span></span>
has_metadata <span class="Constant"><span class="Constant">:</span>metadata</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>breakfast_food</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>wheat_products</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>needs_milk</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>Using the new accessors</p>
<div class="UltraViolet">
<pre class="dawn"> thing <span class="Keyword">=</span> <span class="Support">Thing</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span>
thing<span class="PunctuationSeparator">.</span><span class="Entity">breakfast_food</span> <span class="Keyword">=</span> 'a wheat like cereal"
# same thing, but using the metadata hash directly
thing.metadata[:breakfast_food] = 'a wheat like cereal"
</pre>
</div>
<h3>Ruby 1.9+</h3>
<p>Dynabix under Ruby 1.9+ enables specifying multiple metadata columns on a model. You are
not limited to using the static "metadata" column.</p>
<p>Add text columns "cows" and "chickens" to your "thing" model migration</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">class</span> <span class="Entity">AddMetadataToThings<span class="Entity"> <span class="PunctuationSeparator"><</span> ActiveRecord::Migration</span></span>
<span class="Keyword">def</span> <span class="Entity">change</span>
add_column <span class="Constant"><span class="Constant">:</span>things</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>cows</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>text</span>
add_column <span class="Constant"><span class="Constant">:</span>things</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>chickens</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>text</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>Specifying multiple metadata serializers to segregate like data into separate
database columns (Ruby 1.9 only)</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">class</span> <span class="Entity">Thing<span class="Entity"> <span class="PunctuationSeparator"><</span> ActiveRecord::Base</span></span>
has_metadata <span class="Constant"><span class="Constant">:</span>cows</span>
has_metadata <span class="Constant"><span class="Constant">:</span>chickens</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>tasty</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>feather_count</span>
<span class="Comment"> <span class="Comment">#</span> read-only</span>
cows_reader <span class="Constant"><span class="Constant">:</span>likes_milk</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>hates_eggs</span>
<span class="Comment"> <span class="Comment">#</span> write-only</span>
cows_writer <span class="Constant"><span class="Constant">:</span>no_wheat_products</span>
<span class="Comment"> <span class="Comment">#</span> extra full accessors for chickens</span>
chickens_accessor <span class="Constant"><span class="Constant">:</span>color</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>likes_eggs</span><span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>egg_count</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>Using the new accessors</p>
<div class="UltraViolet">
<pre class="dawn"> thing <span class="Keyword">=</span> <span class="Support">Thing</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span>
<span class="Comment"> <span class="Comment">#</span> cow stuff</span>
thing<span class="PunctuationSeparator">.</span><span class="Entity">no_wheat_products</span> <span class="Keyword">=</span> <span class="Constant">true</span>
<span class="Comment"> <span class="Comment">#</span> chicken stuff</span>
thing<span class="PunctuationSeparator">.</span><span class="Entity">likes_eggs</span> <span class="Keyword">=</span> <span class="Constant">true</span>
thing<span class="PunctuationSeparator">.</span><span class="Entity">egg_count</span> <span class="Keyword">=</span> <span class="Constant">12</span>
<span class="Comment"> <span class="Comment">#</span> using the metadata hash directly to read the data since</span>
<span class="Comment"> <span class="Comment">#</span> we only created a write accessor</span>
thing<span class="PunctuationSeparator">.</span><span class="Entity">cows</span>[<span class="Constant"><span class="Constant">:</span>no_wheat_products</span>]<span class="PunctuationSeparator">.</span><span class="Entity">should</span> be_true
</pre>
</div>
<h3>Runtime dependencies</h3>
<ul>
<li>Activerecord 3.x</li>
</ul>
<h3>Installation</h3>
<p>Add Dynabix to your Gemfile</p>
<pre><code>gem "dynabix"
</code></pre>
<p>Install the gem with Bundler</p>
<pre><code>bundle install
</code></pre>
<h3>Development</h3>
<p>Get the source</p>
<pre><code>cd workspace
git clone https://github.com/robertwahler/dynabix.git
cd dynabix
</code></pre>
<p>Install the dependencies</p>
<pre><code>bundle install
</code></pre>
<p>Run the specs</p>
<pre><code>bundle exec rake spec
</code></pre>
<p>Autotest with Guard</p>
<pre><code>bundle exec guard
</code></pre>
Using Ruby to Automate Windows GUI Applications for Testingtag:www.gearheadforhire.com,2010-11-04:12888959822010-11-04T14:39:42-04:00<p>Presenting <a href="http://rubygems.org/gems/win32-autogui">Win32-autogui</a>. A Ruby Win32
GUI testing framework packaged as a <a href="http://rubygems.org/">RubyGem</a>.</p>
<h3>Overview</h3>
<p>Win32-autogui provides a framework to enable GUI application testing with
<a href="http://www.ruby-lang.org">Ruby</a>. This facilitates integration testing of
Windows binaries using Ruby based tools like
<a href="http://github.com/dchelimsky/rspec">RSpec</a> and
<a href="http://github.com/aslakhellesoy/cucumber">Cucumber</a> regardless of the language
used to create the binaries.</p>
<p>The source code repository is available here:
<a href="http://github.com/robertwahler/win32-autogui">http://github.com/robertwahler/win32-autogui</a>. The repository contains specs
and an example Win32 program with source and specs written in Delphi (Object
Pascal).</p>
<h3>Driving the Window's Calculator Application with IRB</h3>
<p>Here is a quick demo using the Ruby Interactive Shell (IRB) under
<a href="http://www.cygwin.com/">Cygwin</a> on Windows XP to drive "calc.exe."</p>
<h4>Install the Gem</h4>
<p>Win32-autogui is available on <a href="http://rubygems.org/gems/win32-autogui">RubyGems.org</a></p>
<pre><code>gem install win32-autogui
</code></pre>
<h4>IRB Session</h4>
<p>Start up IRB</p>
<pre><code>irb
</code></pre>
<p>Paste the following lines into your shell's IRB session.</p>
<p><strong>Note:</strong> <em>Window's "calc.exe" is used as the target binary by Win32-autogui's
internal specs. The complete source to the wrapper is available here:
<a href="https://github.com/robertwahler/win32-autogui/blob/master/spec/applications/calculator.rb">spec/applications/calculator.rb</a>.</em></p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">require</span> 'win32/autogui'
<span class="Keyword">include</span> <span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">Input</span>
<span class="Keyword">class</span> <span class="Entity">Calculator<span class="Entity"> <span class="PunctuationSeparator"><</span> Autogui::Application</span></span>
<span class="Keyword">def</span> <span class="Entity">initialize</span>
<span class="Keyword">super</span> <span class="Constant"><span class="Constant">:</span>name</span> <span class="PunctuationSeparator">=></span> "calc"<span class="PunctuationSeparator">,</span> <span class="Constant"><span class="Constant">:</span>title</span> <span class="PunctuationSeparator">=></span> "Calculator"
<span class="Keyword">end</span>
<span class="Keyword">def</span> <span class="Entity">edit_window</span>
main_window<span class="PunctuationSeparator">.</span><span class="Entity">children</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> {<span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span> w<span class="PunctuationSeparator">.</span><span class="Entity">window_class</span> <span class="Keyword">==</span> 'Edit'}
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>Now we can start up the calculator</p>
<pre><code>calc = Calculator.new
calc.running?
</code></pre>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/win32-autogui/screenshots/" target="_blank"><img src="/articles/ruby/win32-autogui/screenshots/images/medium/calculator_irb1.png" title="Driving the calculator in IRB (1)" alt="Driving the calculator in IRB (1)" /></a><p class="caption">Driving the calculator in <span class="caps">IRB</span> (1)</p></div>
</div>
<p>Get some information</p>
<pre><code>calc.pid
calc.main_window.window_class
calc.main_window.children.count
</code></pre>
<p>Perform a calculation</p>
<pre><code>calc.set_focus; type_in('2+2=')
</code></pre>
<p>Get the result</p>
<pre><code>calc.edit_window.text
</code></pre>
<p>Shut it down</p>
<pre><code>calc.close
calc.running?
</code></pre>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/win32-autogui/screenshots/" target="_blank"><img src="/articles/ruby/win32-autogui/screenshots/images/medium/calculator_irb2.png" title="Driving the calculator in IRB (2)" alt="Driving the calculator in IRB (2)" /></a><p class="caption">Driving the calculator in <span class="caps">IRB</span> (2)</p></div>
</div>
<h3>RSpec + Win32-autogui for Testable GUI Specifications</h3>
<p>The Win32-autogui repository contains an example Win32 program with
source, testable binary, and specs written in Delphi (Object Pascal) located here:
<a href="http://github.com/robertwahler/win32-autogui/tree/master/examples/quicknote">http://github.com/robertwahler/win32-autogui/tree/master/examples/quicknote</a>.</p>
<p>Quicknote is a bare bones notepad clone. Here is the spec file
<a href="http://github.com/robertwahler/win32-autogui/blob/master/examples/quicknote/spec/quicknote/form_splash_spec.rb">spec/form_splash_spec.rb</a>
for the splash screen functionality.</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">require</span> <span class="Support">File</span><span class="PunctuationSeparator">.</span><span class="Entity">expand_path</span>(<span class="Support">File</span><span class="PunctuationSeparator">.</span><span class="Entity">dirname</span>(<span class="Variable">__FILE__</span>) <span class="Keyword">+</span> '/../spec_helper')
<span class="Keyword">include</span> <span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">Input</span>
describe "FormSplash" <span class="Keyword">do</span>
<span class="Entity">after</span>(<span class="Constant"><span class="Constant">:</span>all</span>) <span class="Keyword">do</span>
<span class="Keyword">if</span> <span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">running?</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span><span class="PunctuationSeparator">.</span><span class="Entity">wait_for_close</span> <span class="Keyword">if</span> <span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">file_exit</span>
<span class="Comment"> <span class="Comment">#</span> still running? force it to close</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">close</span>(<span class="Constant"><span class="Constant">:</span>wait_for_close</span> <span class="PunctuationSeparator">=></span> <span class="Constant">true</span>)
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">should_not</span> be_running
<span class="Keyword">end</span>
<span class="Keyword">end</span>
describe "startup with no command line parameters" <span class="Keyword">do</span>
<span class="Entity">before</span>(<span class="Constant"><span class="Constant">:</span>all</span>) <span class="Keyword">do</span>
<span class="Comment"> <span class="Comment">#</span> --nosplash is the default, turn it back on</span>
<span class="Variable"><span class="Variable">@</span>application</span> <span class="Keyword">=</span> <span class="Support">Quicknote</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span> <span class="Constant"><span class="Constant">:</span>parameters</span> <span class="PunctuationSeparator">=></span> ''
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">should</span> be_running
<span class="Keyword">end</span>
it "should show" <span class="Keyword">do</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span><span class="PunctuationSeparator">.</span><span class="Entity">should_not</span> be_nil
<span class="Keyword">end</span>
it "should close within 5 seconds" <span class="Keyword">do</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span><span class="PunctuationSeparator">.</span><span class="Entity">should_not</span> be_nil
seconds <span class="Keyword">=</span> <span class="Constant">5</span>
<span class="Entity">timeout</span>(seconds) <span class="Keyword">do</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span><span class="PunctuationSeparator">.</span><span class="Entity">wait_for_close</span>
<span class="Keyword">end</span>
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span><span class="PunctuationSeparator">.</span><span class="Entity">should</span> be_nil
<span class="Keyword">end</span>
<span class="Keyword">end</span>
describe "startup with '--nosplash' command line parameter" <span class="Keyword">do</span>
it "should not show" <span class="Keyword">do</span>
<span class="Variable"><span class="Variable">@</span>application</span> <span class="Keyword">=</span> <span class="Support">Quicknote</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span> <span class="Constant"><span class="Constant">:</span>parameters</span> <span class="PunctuationSeparator">=></span> '--nosplash'
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">should</span> be_running
<span class="Variable"><span class="Variable">@</span>application</span><span class="PunctuationSeparator">.</span><span class="Entity">splash</span><span class="PunctuationSeparator">.</span><span class="Entity">should</span> be_nil
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">end</span>
</pre>
</div>
<p>The Quicknote.exe application wrapper. Each of the testable application
windows must be defined in a subclass of Autogui::Application. Partial code
from <a href="http://github.com/robertwahler/win32-autogui/blob/master/examples/quicknote/lib/quicknote.rb">lib/quicknote.rb</a>.</p>
<div class="UltraViolet">
<pre class="dawn"> <span class="Keyword">class</span> <span class="Entity">Quicknote<span class="Entity"> <span class="PunctuationSeparator"><</span> Autogui::Application</span></span>
<span class="Keyword">def</span> <span class="Entity">initialize</span>(<span class="Variable">options <span class="Keyword">=</span> <span class="Variable">{</span><span class="Variable">}</span></span>)
<span class="Comment"> <span class="Comment">#</span> relative path to app using Windows style path</span>
<span class="Variable"><span class="Variable">@</span>name</span> <span class="Keyword">=</span>"exe<span class="StringConstant">\\</span>quicknote.exe"
defaults <span class="Keyword">=</span> {
<span class="Constant"><span class="Constant">:</span>title</span><span class="PunctuationSeparator">=></span> "QuickNote -"<span class="PunctuationSeparator">,</span>
<span class="Constant"><span class="Constant">:</span>parameters</span> <span class="PunctuationSeparator">=></span> '--nosplash'<span class="PunctuationSeparator">,</span>
<span class="Constant"><span class="Constant">:</span>main_window_timeout</span> <span class="PunctuationSeparator">=></span> <span class="Constant">20</span>
}
<span class="Keyword">super</span> defaults<span class="PunctuationSeparator">.</span><span class="Entity">merge</span>(options)
<span class="Keyword">end</span>
<span class="Keyword">def</span> <span class="Entity">edit_window</span>
main_window<span class="PunctuationSeparator">.</span><span class="Entity">children</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> {<span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span> w<span class="PunctuationSeparator">.</span><span class="Entity">window_class</span> <span class="Keyword">==</span> 'TMemo'}
<span class="Keyword">end</span>
<span class="Keyword">def</span> <span class="Entity">status_bar</span>
main_window<span class="PunctuationSeparator">.</span><span class="Entity">children</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> {<span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span> w<span class="PunctuationSeparator">.</span><span class="Entity">window_class</span> <span class="Keyword">==</span> 'TStatusBar'}
<span class="Keyword">end</span>
<span class="Keyword">def</span> <span class="Entity">dialog_about</span>
<span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">EnumerateDesktopWindows</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> <span class="Keyword">do </span><span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span>
w<span class="PunctuationSeparator">.</span><span class="Entity">title</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">About QuickNote</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>) <span class="Keyword">&&</span> (w<span class="PunctuationSeparator">.</span><span class="Entity">pid</span> <span class="Keyword">==</span> pid)
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">def</span> <span class="Entity">splash</span>
<span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">EnumerateDesktopWindows</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> <span class="Keyword">do </span><span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span>
w<span class="PunctuationSeparator">.</span><span class="Entity">title</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">FormSplash</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>) <span class="Keyword">&&</span> (w<span class="PunctuationSeparator">.</span><span class="Entity">pid</span> <span class="Keyword">==</span> pid)
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Keyword">def</span> <span class="Entity">message_dialog_confirm</span>
<span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">EnumerateDesktopWindows</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> <span class="Keyword">do </span><span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span>
w<span class="PunctuationSeparator">.</span><span class="Entity">title</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">Confirm</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>) <span class="Keyword">&&</span> (w<span class="PunctuationSeparator">.</span><span class="Entity">pid</span> <span class="Keyword">==</span> pid)
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Comment"> <span class="Comment">#</span> Title and class are the same as dialog_overwrite_confirm</span>
<span class="Comment"> <span class="Comment">#</span> Use child windows to differentiate</span>
<span class="Keyword">def</span> <span class="Entity">dialog_overwrite_confirm</span>
<span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">EnumerateDesktopWindows</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> <span class="Keyword">do </span><span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span>
w<span class="PunctuationSeparator">.</span><span class="Entity">title</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">^Text File Save$</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>) <span class="Keyword">&&</span>
(w<span class="PunctuationSeparator">.</span><span class="Entity">pid</span> <span class="Keyword">==</span> pid) <span class="Keyword">&&</span>
(w<span class="PunctuationSeparator">.</span><span class="Entity">window_class</span> <span class="Keyword">==</span> "#32770") <span class="Keyword">&&</span>
(w<span class="PunctuationSeparator">.</span><span class="Entity">combined_text</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">already exists</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>))
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="Comment"> <span class="Comment">#</span> Title and class are the same as dialog_overwrite_confirm</span>
<span class="Keyword">def</span> <span class="Entity">file_save_as_dialog</span>
<span class="Support">Autogui</span><span class="PunctuationSeparator">::</span><span class="Entity">EnumerateDesktopWindows</span><span class="PunctuationSeparator">.</span><span class="Entity">new</span><span class="PunctuationSeparator">.</span><span class="Entity">find</span> <span class="Keyword">do </span><span class="PunctuationSeparator">|</span><span class="Variable">w</span><span class="PunctuationSeparator">|</span>
w<span class="PunctuationSeparator">.</span><span class="Entity">title</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">Text File Save</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>) <span class="Keyword">&&</span>
(w<span class="PunctuationSeparator">.</span><span class="Entity">pid</span> <span class="Keyword">==</span> pid) <span class="Keyword">&&</span>
(w<span class="PunctuationSeparator">.</span><span class="Entity">window_class</span> <span class="Keyword">==</span> "#32770") <span class="Keyword">&&</span>
(w<span class="PunctuationSeparator">.</span><span class="Entity">combined_text</span><span class="PunctuationSeparator">.</span><span class="Entity">match</span>(<span class="StringRegexp"><span class="StringRegexp">/</span></span><span class="StringRegexp">Save <span class="StringRegexpConstantCharacterEscape">\&</span>in:</span><span class="StringRegexp"><span class="StringRegexp">/</span></span>))
<span class="Keyword">end</span>
<span class="Keyword">end</span>
<span class="PunctuationSeparator">.</span><span class="PunctuationSeparator">.</span><span class="PunctuationSeparator">.</span>
</pre>
</div>
<h4>Autotesting Quicknote with Watchr</h4>
<p><a href="http://github.com/mynyml/watchr">Watchr</a> provides a flexible alternative to Autotest.</p>
<p><strong>NOTE:</strong> <em>The following assumes a global setting of 'git config core.autocrlf
input' and that you want to modify the Delphi 7 source to Quicknote which
requires CRLF line endings.</em></p>
<p>Grab the source for Quicknote</p>
<pre><code>cd ~/workspace
git clone http://github.com/robertwahler/win32-autogui -n
cd win32-autogui
git config core.autocrlf true
git checkout
</code></pre>
<p>Install watchr</p>
<pre><code>gem install watchr
</code></pre>
<p>Run watchr</p>
<pre><code>watchr spec/watchr.rb
</code></pre>
<p>Watchr will now watch the files defined in 'spec/watchr.rb' and run RSpec or Cucumber, as appropriate.</p>
<p>Session screenshot</p>
<div class="photobar">
<div style="width: 640px; "class="photo"><a href="/articles/ruby/win32-autogui/screenshots/" target="_blank"><img src="/articles/ruby/win32-autogui/screenshots/images/medium/quicknote_splash_rspec.png" title="Watchr session running form_splash_spec.rb" alt="Watchr session running form_splash_spec.rb" /></a><p class="caption">Watchr session running form_splash_spec.rb</p></div>
</div>
<p>For more information, please consult the source: <a href="http://github.com/robertwahler/win32-autogui">http://github.com/robertwahler/win32-autogui</a>.</p>
Using Git to Maintain Common RubyGem Functionality with BasicGemtag:www.gearheadforhire.com,2010-10-13:12869803292010-10-13T10:32:09-04:00<p>Do you maintain several different <a href="http://rubygems.org/">RubyGems</a>? Maybe you maintain dozens? Wouldn't it be nice to not have to repeat yourself when making changes that should be common to all the gems in your stable? For example, you decide that going forward, you will use <a href="http://github.com/carlhuda/bundle">Bundler</a> for all your gem dependency needs. You could tweak your gemspecs and Rakefiles for each of your gems individually or you could use your customized fork of <a href="http://github.com/robertwahler/basic_gem">BasicGem</a> as a common ancestor for all your gems. Now you can modify your <a href="http://github.com/robertwahler/basic_gem">BasicGem</a> fork and merge these tweaks using <a href="http://git-scm.com/">Git</a> into all your gems. As simple as...</p>
<pre><code>cd ~/workspace/my_gem_cloned_from_my_basic_gem_fork
git pull my_basic_gem_fork HEAD
git mergetool
</code></pre>
<h3>Introducing BasicGem, Gem Maintenance with Git</h3>
<p><a href="http://github.com/robertwahler/basic_gem">BasicGem</a> is an opinionated RubyGem structure. BasicGem provides no stand-alone functionality. Its purpose is to provide a repository for jump-starting a new RubyGem and to provide a repository for cloned applications to pull future enhancements and fixes.</p>
<h3>Features/Dependencies</h3>
<ul>
<li>Bundler for dependency management <a href="http://github.com/carlhuda/bundler">http://github.com/carlhuda/bundler</a></li>
<li>Rspec for unit testing <a href="http://github.com/dchelimsky/rspec">http://github.com/dchelimsky/rspec</a></li>
<li>Cucumber for functional testing <a href="http://github.com/aslakhellesoy/cucumber">http://github.com/aslakhellesoy/cucumber</a></li>
<li>Aruba for CLI testing <a href="http://github.com/aslakhellesoy/aruba">http://github.com/aslakhellesoy/aruba</a></li>
<li>YARD for documentation generation <a href="http://github.com/lsegal/yard">http://github.com/lsegal/yard</a></li>
</ul>
<h3>Example Usage, Jump-starting a New Gem with BasicGem</h3>
<p>The following steps illustrate creating a new gem called "mutagem" that handles file based mutexes.
See <a href="http://github.com/robertwahler/mutagem">http://github.com/robertwahler/mutagem</a> for full source.</p>
<p><strong>NOTE:</strong> <em>We are cloning from <a href="http://github.com/robertwahler/basic_gem">BasicGem</a> directly. Normally, you will want to clone from your own fork of BasicGem so that you can control and fine-tune which future BasicGem modifications you will support.</em></p>
<pre><code>cd ~/workspace
git clone git://github.com/robertwahler/basic_gem.git mutagem
cd mutagem
</code></pre>
<h4>Setup the repository for the cloned project</h4>
<p>We are going to change the origin URL to our own server and setup a remote
for pulling in future BasicGem changes. If our own repo for your new gem is setup at
git@red:mutagem.git, change the URL with sed:</p>
<pre><code>sed -i 's/url =.*\.git$/url = git@red:mutagem.git/' .git/config
</code></pre>
<p>Push up the unchanged BasicGem repo</p>
<pre><code>git push origin master:refs/heads/master
</code></pre>
<p>Allow Gemlock.lock to be stored in the repo</p>
<pre><code>sed -i '/Gemfile\.lock$/d' .gitignore
</code></pre>
<p>Add BasicGem (or your fork of BasicGem) as remote for future merges</p>
<pre><code>git remote add basic_gem git://github.com/robertwahler/basic_gem.git
</code></pre>
<h4>Rename your gem</h4>
<p>Change the name of the gem from basic_gem to mutagem. Note that
renames will be tracked in future merges since Git is tracking content and
the content is non-trivial.</p>
<pre><code>git mv lib/basic_gem.rb lib/mutagem.rb
git mv basic_gem.gemspec mutagem.gemspec
# commit renames now
git commit -m "rename basic_gem files"
# BasicGem => Mutagem
find . -name *.rb -exec sed -i 's/BasicGem/Mutagem/' '{}' +
find . -name *.feature -exec sed -i 's/BasicGem/Mutagem/' '{}' +
sed -i 's/BasicGem/Mutagem/' Rakefile
sed -i 's/BasicGem/Mutagem/' mutagem.gemspec
# basic_gem => mutagem
find ./spec -type f -exec sed -i 's/basic_gem/mutagem/' '{}' +
find . -name *.rb -exec sed -i 's/basic_gem/mutagem/' '{}' +
find . -name *.feature -exec sed -i 's/basic_gem/mutagem/' '{}' +
sed -i 's/basic_gem/mutagem/' Rakefile
sed -i 's/basic_gem/mutagem/' mutagem.gemspec
</code></pre>
<h4>Replace TODO's and update documentation</h4>
<ul>
<li>Replace README.markdown</li>
<li>Replace HISTORY.markdown</li>
<li>Replace TODO.markdown</li>
<li>Replace LICENSE</li>
<li>Replace VERSION</li>
<li>Modify .gemspec, add author information and replace the TODO's</li>
</ul>
<h4>Your gem should now be functional</h4>
<pre><code>rake spec
rake features
</code></pre>
<h4>Setup git copy-merge</h4>
<p>When we merge future BasicGem changes to our new gem, we want to always ignore
some upstream documentation file changes.</p>
<p>Set the merge type for the files we want to ignore in .git/info/attributes. You
could specify .gitattributes instead of .git/info/attributes but then if your
new gem is forked, your forked repos will miss out on document merges.</p>
<pre><code>echo "README.markdown merge=keep_local_copy" >> .git/info/attributes
echo "HISTORY.markdown merge=keep_local_copy" >> .git/info/attributes
echo "TODO.markdown merge=keep_local_copy" >> .git/info/attributes
echo "LICENSE merge=keep_local_copy" >> .git/info/attributes
echo "VERSION merge=keep_local_copy" >> .git/info/attributes
</code></pre>
<p>Setup the copy-merge driver. The "trick" is that the driver, keep_local_copy, is using
the shell command "true" to return exit code 0. Basically, the files marked with
the keep_local_copy merge type will always ignore upstream changes if a merge conflict occurs.</p>
<pre><code>git config merge.keep_local_copy.name "always keep the local copy during merge"
git config merge.keep_local_copy.driver "true"
</code></pre>
<h4>Commit</h4>
<pre><code>git add Gemfile.lock
git commit -a -m "renamed basic_gem to mutagem"
</code></pre>
<h4>Add code to project's namespace</h4>
<pre><code>mkdir lib/mutagem
vim lib/mutagem/mutex.rb
</code></pre>
<h3>Merging Future BasicGem Changes</h3>
<p>Cherry picking method</p>
<pre><code>git fetch basic_gem
git cherry-pick a0f9745
</code></pre>
<p>Merge 2-step method</p>
<pre><code>git fetch basic_gem
git merge basic_gem/master
</code></pre>
<p>Trusting pull of HEAD</p>
<pre><code>git pull basic_gem HEAD
</code></pre>
<p>Conflict resolution</p>
<p><strong>NOTE:</strong> <em>Most conflicts can be resolved with 'git mergetool' but 'CONFLICT (delete/modify)' will
need to be resolved by hand.</em></p>
<pre><code>git mergetool
git commit
</code></pre>
<h3>BasicGem Provided Rake Tasks</h3>
<p>rake -T</p>
<pre><code>rake build # Build mutagem-0.0.1.gem into the pkg directory
rake doc:clean # Remove generated documenation
rake doc:generate # Generate YARD Documentation
rake features # Run Cucumber features
rake install # Build and install mutagem-0.0.1.gem into system gems
rake release # Create tag v0.0.1 and build and push mutagem-0.0.1.gem to Rubygems
rake spec # Run specs
rake test # Run specs and features
</code></pre>
<h3>Autotesting with Watchr</h3>
<p><a href="http://github.com/mynyml/watchr">Watchr</a> provides a flexible alternative to Autotest. A
jump start script is provided in spec/watchr.rb.</p>
<h4>Install watchr</h4>
<pre><code>gem install watchr
</code></pre>
<h4>Run watchr</h4>
<pre><code>watchr spec/watchr.rb
</code></pre>
<p>outputs a menu</p>
<pre><code>Ctrl-\ for menu, Ctrl-C to quit
</code></pre>
<p>Watchr will now watch the files defined in 'spec/watchr.rb' and run Rspec or Cucumber, as appropriate.
The watchr script provides a simple menu.</p>
<p>Ctrl-\</p>
<pre><code>MENU: a = all , f = features s = specs, l = last feature (none), q = quit
</code></pre>
Compiling EncFS for Ubuntu 8.04 LTS (Hardy Heron)tag:www.gearheadforhire.com,2010-03-25:12695392372010-03-25T13:47:17-04:00<h3>The Task</h3>
<p>You are doing user-space filesystem encryption. You want to use a more recent
version of EncFS than the one provided in the Ubuntu 8.04 repositories. No problem,
just compile one yourself.</p>
<h3>The Problem</h3>
<p>The most recent version in the EncFS will not compile on Ubuntu 8.04. Version
r53 12/7/09 configure.ac breaks with:</p>
<pre><code>checking whether xattr interface takes additional options... no
./configure: line 24466: syntax error near unexpected token 'newline'
</code></pre>
<p>This issue has been reported to the EncFS maintainer, in the interim, you can
compile a fairly recent version by following the steps below.</p>
<h3>Reference Links</h3>
<ul>
<li><a href="http://www.arg0.net/encfs">http://www.arg0.net/encfs</a></li>
<li><a href="http://code.google.com/p/encfs/source/checkout">http://code.google.com/p/encfs/source/checkout</a></li>
</ul>
<h3>Preparation</h3>
<p>Get the build tools</p>
<pre><code>sudo apt-get install build-essential autoconf automake1.9 libtool gettext \
cvs pkg-config
</code></pre>
<p>Verify the kernel has FUSE support</p>
<pre><code>cat /proc/filesystems | grep fuse
</code></pre>
<p>you should see something like this:</p>
<pre><code>nodev fuse
fuseblk
nodev fusectl
</code></pre>
<p>Install EncFS dependencies</p>
<pre><code>sudo apt-get install libboost-dev libboost-filesystem-dev \
libboost-serialization-dev libfuse-dev \
fuse-utils librlog-dev libssl-dev
</code></pre>
<h3>Building</h3>
<p>Build version SVN r50 (f97ae2780) by pulling down with git and checking out the
most recent version that will compile on Ubuntu 8.04</p>
<div class="UltraViolet">
<pre class="dawn"> cd ~/src
git-svn clone --no-metadata <span class="MarkupUnderline">http://encfs.googlecode.com/svn/trunk</span> encfs
cd encfs
git checkout -b work_around_build f97ae2780
autoreconf -if
./configure
make
sudo make prefix=/usr install
</pre>
</div>
<p>Done!</p>