Piston is a utility that eases vendor branch management. This is similar to svn:externals, except you have a local copy of the files, which you can modify at will. As long as the changes are mergeable, you should have no problems. This tool has a similar purpose than svnmerge.py which you can find in the contrib/client-side folder of the main Subversion repository at http://svn.collab.net/repos/svn/trunk/contrib/client-side/svnmerge.py. The main difference is that Piston is designed to work with remote repositories. Another tool you might want to look at, SVK, which you can find at http://svk.elixus.org/. From Wikipedia's Piston page (http://en.wikipedia.org/wiki/Piston): In general, a piston is a sliding plug that fits closely inside the bore of a cylinder. Its purpose is either to change the volume enclosed by the cylinder, or to exert a force on a fluid inside the cylinder. For this utility, I retain the second meaning, "to exert a force on a fluid inside the cylinder." Piston forces the content of a remote repository location back into our own. = Installation Nothing could be simpler: $ gem install --include-dependencies piston = Usage First, you need to import the remote repository location: $ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails Exported r4720 from 'http://dev.rubyonrails.org/svn/rails/trunk' to 'vendor/rails' $ svn commit -m "Importing local copy of Rails" When you want to get the latest changes from the remote repository location: $ piston update vendor/rails Updated 'vendor/rails' to r4720. $ svn commit -m "Updates vendor/rails to the latest revision" You can prevent a local Piston-managed folder from updating by using the +lock+ subcommand: $ piston lock vendor/rails 'vendor/rails' locked at r4720. When you want to update again, you unlock: $ piston unlock vendor/rails 'vendor/rails' unlocked. If the branch you are following moves, you should use the switch subcommand: $ piston import http://dev.rubyonrails.org/svn/rails/branches/1-2-pre-release vendor/rails $ svn commit vendor/rails # Vendor branch is renamed, let's follow it $ piston switch http://dev.rubyonrails.org/svn/rails/branches/1-2-stable vendor/rails = Contributions == Bash Shell Completion Script Michael Schuerig contributed a Bash shell completion script. You should copy +contrib/piston+ from your gem repository to the appropriate folder. Michael said: I've put together a bash completion function for piston. On Debian, I just put it in /etc/bash_completion.d, alternatively, the contents can be copied to ~/.bash_completion. I don't know how things are organized on other Unix/Linux systems. = Caveats == Speed This tool is SLOW. The update process particularly so. I use a brute force approach. Subversion cannot merge from remote repositories, so instead I checkout the folder at the initial revision, and then run svn update and parse the results of that to determine what changes have occured. If a local copy of a file was changed, it's changes will be merged back in. If that introduces a conflict, Piston will not detect it. The commit will be rejected by Subversion anyway. == Copies / Renames Piston *does not* track copies. Since Subversion does renames in two phases (copy + delete), that is what Piston does. == Local Operations Only Piston only works if you have a working copy. It also never commits your working copy directly. You are responsible for reviewing the changes and applying any pending fixes. == Remote Repository UUID Piston caches the remote repository UUID, allowing it to know if the remote repos is still the same. Piston refuses to work against a different repository than the one we checked out from originally. = Subversion Properties Used * piston:uuid: The remote repository's UUID, which we always confirm before doing any operations. * piston:root: The repository root URL from which this Piston folder was exported from. * piston:remote-revision: The Last Changed Rev of the remote repository. * piston:local-revision: The Last Changed Rev of the Piston managed folder, to enable us to know if we need to do any merging. * piston:locked: The revision at which this folder is locked. If this property is set and non-blank, Piston will skip the folder with an appropriate message. = Dependencies Piston depends on the following libraries: * yaml * uri * fileutils