externals-tutorial
What is externals and what is it used for?
externals allows you to make use of an svn:externals-like workflow with any combination of SCMs. What is the svn:externals workflow? I would describe it roughly like this:
You register subprojects with your main project. When you checkout the main project, the subprojects are automatically checked out. Doing a ‘status’ will tell you the changes in the main projects and any subprojects from where it’s ran. You commit changes to the the projects all seperately as needed. If somebody else does an update, they will get the changes to the subprojects as well.
For a more detailed explanation of why I started the externals project, please visit http://nopugs.com/why-ext It’s largely a rant about git-submodule.
On with the tutorial
Installation
ext should run on unix-like systems and windows systems. All the unit tests pass on Linux and Windows vista (with cygwin).
First we need to install externals. The first, and easiest, method is to use gem:
gem install ext
The other method is to use github:
git clone git://github.com/azimux/externals.git chmod u+x externals/bin/ext
If you install using git clone instead of rubygems, be sure to add the externals/bin directory to your path.
Creating a repository to play around with
I will use git for the main project, and will use git and subversion for the subprojects (the tutorial would be mostly identical if I used svn for the main project, that’s part of the point of ext.)
Now let’s create a repository for use with our project. I like to test out stuff like this in my ~/tmp/ folder.
cd mkdir tmp cd tmp mkdir repo mkdir work cd repo mkdir rails_app.git cd rails_app.git git init --bare
Now let’s go to our work directory and make a rails app to push to this repository.
cd ../../work/ rails rails_app cd rails_app git init git add . git commit -m "created fresh rails app" git remote add origin ../../repo/rails_app.git git push origin master
If you’re like me, you consider empty directories in your project’s directory structure to be part of the project. Git will not track empty directories. So, here’s our first use of ext:
ext touch_emptydirs git add . git commit -m "touched empty dirs" git push
This adds a .emptydir file to every empty directory so that git will track these folders.
Using “ext install” to register subprojects.
Now for our second use of ext. Let’s add the current edge rails to our application:
ext install git://github.com/rails/rails.git
It should take a moment because rails is a large project.
Now that that’s done, let’s see what “ext install” did.
$ cat .externals [.] scm = git type = rails [vendor/rails] path = vendor/rails repository = git://github.com/rails/rails.git scm = git
.externals is the externals configuration file. This is the file used to keep track of your subprojects. Projects are stored in the form:
[path/to/project] repository = urlfor://project.repository/url branch = somebranch scm = git/svn
The format is very similar to ini format. The section name is the path to the project. The main project’s settings are stored under [.]
Some things to notice: externals was automatically able to figure out that we’re using git for the main project (scm = git under [.]) Also, note that the type of the main project has been detected as rails (type = rails) This means that we can leave the paths off of the repositories in .externals (when using “ext install”) and ext will automatically know where to install stuff (if it’s called rails it goes in vendor/rails otherwise it goes in vendor/plugins/) Let’s make sure it’s there.
$ ls vendor/rails Rakefile activemodel activesupport pushgems.rb actionmailer activerecord ci railties actionpack activeresource doc release.rb
That’s not all, take a look at the ignore file:
$ cat .gitignore vendor/rails
This makes sense because we don’t want the main repository to track any of the files in the subproject. The files in the subproject are tracked by their own repository, possibly of a different SCM than the main project.
Let’s add some more subprojects: some rails plugins this time. We’ll add a couple that are tracked under subversion and one tracked under git to demnostrate how ext is scm agnostic.
ext install git://github.com/lazyatom/engines -b edge ext install svn://rubyforge.org/var/svn/redhillonrails/trunk/vendor/plugins/redhillonrails_core ext install svn://rubyforge.org/var/svn/redhillonrails/trunk/vendor/plugins/foreign_key_migrations
let’s see if our plugins made it
$ du --max-depth=2 -h vendor/plugins/ | grep lib 252K vendor/plugins/foreign_key_migrations/lib 340K vendor/plugins/redhillonrails_core/lib 24K vendor/plugins/engines/lib
looks good
$ cat .externals [.] scm = git type = rails [vendor/rails] path = vendor/rails repository = git://github.com/rails/rails.git scm = git [vendor/plugins/engines] path = vendor/plugins/engines repository = git://github.com/lazyatom/engines scm = git branch = edge [vendor/plugins/redhillonrails_core] path = vendor/plugins/redhillonrails_core repository = svn://rubyforge.org/var/svn/redhillonrails/trunk/vendor/plugins/red hillonrails_core scm = svn [vendor/plugins/foreign_key_migrations] path = vendor/plugins/foreign_key_migrations repository = svn://rubyforge.org/var/svn/redhillonrails/trunk/vendor/plugins/for eign_key_migrations scm = svn
…and the ignore file…
$ cat .gitignore vendor/rails vendor/plugins/acts_as_list vendor/plugins/foreign_key_migrations vendor/plugins/redhillonrails_core
also looks very good!
Something worth noting: if we were using svn for our main project, ext is smart enough to set the ignores using ‘svn propset svn:ignore’ on the appropriate directories.
Let’s now commit and push our work.
git add . git commit -m "added 4 subprojects" git push
Using “ext checkout” and “ext export”
And now let’s delete and check it out again to make sure we get the sub projects
cd .. rm -rf rails_app ext checkout ../repo/rails_app.git
It will take a moment as it clones rails from github again.
Let’s make sure all of the subprojects were checked out properly:
$ cd rails_app $ du --max-depth=3 -h vendor/ | grep lib 12K vendor/plugins/acts_as_list/lib 66K vendor/plugins/foreign_key_migrations/lib 162K vendor/plugins/redhillonrails_core/lib 382K vendor/rails/actionmailer/lib 1.5M vendor/rails/actionpack/lib 104K vendor/rails/activemodel/lib 791K vendor/rails/activerecord/lib 92K vendor/rails/activeresource/lib 2.4M vendor/rails/activesupport/lib 584K vendor/rails/railties/lib
let’s also make sure the engines plugin is on a branch called “edge” (which is tracking the remote repository’s edge branch)
$ cd vendor/plugins $ git branch -a * edge master origin/HEAD origin/add_test_for_rake_task_redefinition origin/edge origin/master origin/timestamped_migrations
Notice how the subprojects were automatically fetched. As mentioned in the why ext article, the main project is usually incapable of functioning without it’s subprojects, so it makes sense to fetch the subprojects when we do a checkout or export. (This is what svn checkout does when it checks out a folder that has svn:externals set on it. It fetches the external projects automatically, which is very convenient.)
Note that you can use “ext export” instead of checkout if you don’t want histories to accompany the files. This tells ext to use “svn export” for subversion managed (sub)projects and “git clone –depth 1” for git managed (sub)projects. This can save a lot of time and is useful for deployment.
looks good, let’s go back to the rails_app directory to continue the tutorial
cd ../../../
“ext status” propagates through subprojects
Let’s modify a subproject.
echo "lol, internet" >> vendor/plugins/foreign_key_migrations/README
And now let’s check the status
$ ext status status for .: # On branch master nothing to commit (working directory clean) status for vendor/rails: # On branch master nothing to commit (working directory clean) status for vendor/plugins/acts_as_list: # On branch master nothing to commit (working directory clean) status for vendor/plugins/redhillonrails_core: status for vendor/plugins/foreign_key_migrations: M README
As expected, foreign_key_migrations has a modified file. This same (very common) task is a bit of a pain in the neck with git-submodule (unless I’m missing something), and impossible in this situation where the subproject is not managed under the same source control system as the main project (as in this example.)
Deployment with capistrano
Most commands also have a short version of the command. The short versions only operate on the subprojects and not the main projects. “ext checkout” or “ext export” fetches the main project and subprojects but “ext co” and “ext ex” (meant to be ran in the working folder of the main project, use –workdir to do it from elsewhere) will fetch all subprojects and doesn’t touch the main project.
If you deploy with capistrano, you can have all your subprojects fetched on deployment by adding the following to your deploy.rb:
task :after_update_code, :roles => :app do
run "ext --workdir #{release_path} ex"
endNotice how I chose to use “ex” instead of “co” This is because I never do work from a deployed project’s working directory, so the history is pointless.
If people find externals usefull, I’d be happy to add a :ext scm type to capistrano so that it runs ext instead of git/svn. Then it would pickup all the subprojects during a deploy without having to supply the above after_update_code task. I could also add a switch to rails “./script/plugin install” (perhaps -X) to tell it to use ext to manage the project (kind of how you can use -x to tell it to use svn:externals.) Though, this isn’t really any easier to make use of than just doing “ext install”
A few other tips
“ext help” will show you all the available commands. Also, feel free to manage the .externals file manually if you wish.
Conclusion
For issue tracking, at the moment I’m using lighthouseapp. Report bugs to http://ext.lighthouseapp.com/
I also have a rubyforge account for this project at http://rubyforge.org/projects/ext/ if you would prefer to submit bugs/feature requests via rubyforge’s tracking system. I’ve used both sites but never managed a project with either, so I don’t know which is better. Rubyforge seems to be more feature complete.
Externals is my first attempt at contributing a useful open source project to the community. If you have some tips for me in this regard, please feel free to share them.
Cheers!
Posted in externals, Ruby, Ruby on Rails | 7 comments |
Trackbacks<
Use the following link to trackback from your own site:
http://nopugs.com/trackbacks?article_id=10
30/06/2009 at 00h00
Hi,
ext looks great and it seems like it may be exactly what I’m looking for! I’m in the process of moving from svn to git for my source control and there is lots of stuff about git that I love. But the lack on svn:externals and partial checkouts is really annoying and I’m trying to figure out how I need to change my workflow. It looks like maybe I can just adopt ext and keep more or less my existing workflow!
I have one question. Does an ext project need to be checked out with ext? So if I want someone else to be able to grab my project and work on it then I need to tell them to install ext and use the “ext checkout” command after they have done a “git clone” to get the initial repository? I presume so but I’m just checking…
Thanks!
Kelvin :)
30/06/2009 at 00h19
Hi Kevin,
If the part of the project they are going to work on is dependent on other projects, then yeah they’d need to use ext. Well, they don’t HAVE to use it, they could certainly manually checkout the dependencies.
One other thing, “ext checkout” should clone the initial repository as well. So they don’t need to do an initial git clone.
I hope it’s useful to you :)
Miles
09/06/2010 at 11h18
This is fawesome. You rock.
26/08/2010 at 07h17
Hi Miles,
Thanks! Great work. This addresses a major shortcoming.
Is there support: 1. For relative URL paths or variables/tokens that relate back to the initial project’s path, URL or even protocol (similar to svn:externals containing ‘^/’)? 2. To check out all externals with the same user-name as used by the initial project?
If not, how can I convince/help you to add it?
15/09/2010 at 11h46
Hey Pieter, sorry for taking so long to reply.
Unfortunately, there’s not support for either of those features you mentioned.
They wouldn’t really be particularly useful features to me personally. Therefore, I have little motivation in adding them myself.
I suppose this might be mildly useful in a situation where a subproject is stored in a subdirectory at the main project’s repository. I’m not exactly sure why somebody would want to do this. Perhaps somebody had an existing plugin in another project, that then became a repository of it’s own, but didn’t change locations? In the situation where a plugin in one application becomes useful elsewhere, I move the plugin to it’s own location and have both projects point at it. I don’t have one project point to a folder in the plugins directory of another project.
It seems like having the user specified explicitly in the .externals file would probably only be useful for projects being worked on by 1 person. After all, if I checked out one of your projects, why would I want to use your username? But that’d be what would happen, unless it was programmed to use all other methods of guessing the username first, but one of these would be to use the current user’s login, which would never fail to exist. So all usernames seem like they’d become explicit.
I’m not trying to say that you do not have legitimate uses for such features, it’s entirely possible that you want to use them in reasonable scenarios that haven’t crossed my mind yet. But since these haven’t yet crossed my mind, I feel little motivation for adding them.
Miles
07/11/2011 at 13h02
I’m trying to checkout doctrine1 from git and hav eit use the branch 1.2.4 but when i use:
branch = 1.2.4
i see:
fatal: git checkout: updating paths is incompatible with switching branches.
Help!
09/11/2011 at 08h35
Hey Jon, this is a message from git, not from ext.
Is your current branch out of date or has modifications maybe? I think that can cause this type of problem.
If you can file an issue on github with a little information about what you’re doing, I’d be more than happy to try and help you resolve it.
Miles