Commit e5efa2be6bec95cf6b56199e3213e8e4079ee422

Initial implementation of Piston 2, built using newgem. New Repository and WorkingCopy objects, all implemented using BDD.

git-svn-id: svn+ssh://rubyforge.org/var/svn/piston/trunk@116 d6c2ea82-c31b-0410-8381-e9c44f9824c5

Commit diff

History.txt

 
1*SVN*
2
3* New Piston2
4
52007-06-28 branches/piston1 -- NEVER RELEASED
6* Per http://rubyforge.org/tracker/?func=detail&atid=8179&aid=10717&group_id=2105
7 Don't set LC_ALL, but set LANGUAGE so that repositories with foreign
8 characters can be used. Thanks go to Per Wigren.
9
102007-03-22 1.3.3
11* Repaired problems with import subcommand. Wrote specifications to prevent
12 the same failure mode again.
13
142007-03-09 1.3.2
15* piston switch had a bad constant access which caused failures.
16
172007-03-09 1.3.1
18* piston switch would fail if the branch from which we are reading had been
19 deleted.
20* piston switch had a major bug. It did not update the piston:root property
21 to remember the new repository root. Reported and fixed by Graeme
22 Mathieson.
23* piston switch errors out early if not provided with the right arguments.
24 Thanks to Graeme Mathieson for the info and patch.
25* New internal command parser. No visible external changes.
26
272007-01-22 1.3.0
28* Piston status shows the revision number of locked repositories. Thanks to
29 Chris Wanstrath <http://errtheblog.com/>.
30* New piston switch subcommand to switch repository locations. Thanks to
31 Greg Spurrier for the prompt which resulted in finally implementing this.
32
332006-11-20 1.2.1
34* Import subcommand would fail with a "svn: Explicit target required
35 ('vendor/rails' interpreted as prop value)" error. This was a minor
36 error in the import code. Reported by Daniel N.
37* The import subcommand could import another revision than what was intended,
38 if HEAD was updated while the import is in progress.
39
402006-11-17 1.2.0
41* New status subcommand. Shows M if locally or remotely modified. Applies to
42 one, many, all folders. This subcommand *requires* the use of a Subversion
43 1.2.0 client. Thanks to Chris Wanstrath for the inspiration. His Rake
44 tasks are available at http://errtheblog.com/post/38.
45* Minor patch by Miguel Ibero Carreras to make Subversion always use the
46 C locale, instead of the current one. This allows Piston to be used
47 with internationalized versions of Subversion. David Bittencourt later
48 reported the same problem. Thanks!
49* Better handle how update finds it's latest local revision to prevent
50 conflicts. If you had never locally changed your vendor repositories,
51 this fix will change nothing for you. This helps prevent local conflicts
52 if you had ever applied a local patch.
53 *CAVEAT*: See the release announcement at
54 http://blog.teksol.info/articles/2006/11/17/piston-1-2-0-status-better-update
55 for a required local operation.
56
572006-08-30 1.1.1
58* Add contrib/piston [Michael Schuerig]
59* Non-recursively add the root directory of the managed folder then set Piston
60 properties before adding the contents of the managed folder. This is to
61 help ease work along if an inconsistent EOL is encountered during the
62 import. The user can finish the import by svn add'ing the rest of the
63 folder until all files are added. Piston properties will already have been
64 set.
65
662006-08-26 1.1.0
67* New 'convert' subcommand converts existing svn:externals to Piston managed
68 folders. Thanks to Dan Kubb for the idea.
69* update now recursively finds the folders to process. It bases it's search
70 on the presence or absence of the piston:root property.
71* Changed lock and unlock messages to be more detailed.
72
732006-08-24 1.0.1
74* Corrected minor bug where the core extensions were in core_ext/core_ext
75 instead of being in core_ext.
76* Require the parent working copy path be at HEAD before importing / updating.
77* Don't do unnecessary merges if the file had not changed prior to the update.
78* During the update, if adding a folder, do an svn mkdir instead of a cp_r.
79
802006-08-24 1.0.0
81* Initial version
toggle raw diff

License.txt

 
1Copyright (c) 2006 Francois Beausoleil <francois@teksol.info>
2
3Permission is hereby granted, free of charge, to any person obtaining a copy
4of this software and associated documentation files (the "Software"), to deal
5in the Software without restriction, including without limitation the rights
6to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7copies of the Software, and to permit persons to whom the Software is
8furnished to do so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in
11all copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19THE SOFTWARE.
toggle raw diff

Manifest.txt

 
1History.txt
2License.txt
3Manifest.txt
4README.txt
5Rakefile
6lib/piston.rb
7lib/piston/version.rb
8scripts/txt2html
9setup.rb
10spec/helper.rb_spec
11spec/piston.rb_spec
12website/index.html
13website/index.txt
14website/javascripts/rounded_corners_lite.inc.js
15website/stylesheets/screen.css
16website/template.rhtml
toggle raw diff

README.txt

 
1Piston is a utility that eases vendor branch management.
2This is similar to <tt>svn:externals</tt>, except you have a local copy of
3the files, which you can modify at will. As long as the changes are
4mergeable, you should have no problems.
5
6This tool has a similar purpose than svnmerge.py which you can find in the
7contrib/client-side folder of the main Subversion repository at
8http://svn.collab.net/repos/svn/trunk/contrib/client-side/svnmerge.py.
9The main difference is that Piston is designed to work with remote
10repositories. Another tool you might want to look at, SVK, which you can find
11at http://svk.elixus.org/.
12
13From Wikipedia's Piston page (http://en.wikipedia.org/wiki/Piston):
14 In general, a piston is a sliding plug that fits closely inside the bore
15 of a cylinder.
16
17 Its purpose is either to change the volume enclosed by the cylinder, or
18 to exert a force on a fluid inside the cylinder.
19
20For this utility, I retain the second meaning, "to exert a force on a fluid
21inside the cylinder." Piston forces the content of a remote repository
22location back into our own.
23
24
25= Installation
26
27Nothing could be simpler:
28
29 $ gem install --include-dependencies piston
30
31
32= Usage
33
34First, you need to import the remote repository location:
35
36 $ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails
37 Exported r4720 from 'http://dev.rubyonrails.org/svn/rails/trunk' to 'vendor/rails'
38
39 $ svn commit -m "Importing local copy of Rails"
40
41When you want to get the latest changes from the remote repository location:
42
43 $ piston update vendor/rails
44 Updated 'vendor/rails' to r4720.
45
46 $ svn commit -m "Updates vendor/rails to the latest revision"
47
48You can prevent a local Piston-managed folder from updating by using the
49+lock+ subcommand:
50
51 $ piston lock vendor/rails
52 'vendor/rails' locked at r4720.
53
54When you want to update again, you unlock:
55
56 $ piston unlock vendor/rails
57 'vendor/rails' unlocked.
58
59If the branch you are following moves, you should use the switch subcommand:
60
61 $ piston import http://dev.rubyonrails.org/svn/rails/branches/1-2-pre-release vendor/rails
62 $ svn commit vendor/rails
63
64 # Vendor branch is renamed, let's follow it
65 $ piston switch http://dev.rubyonrails.org/svn/rails/branches/1-2-stable vendor/rails
66
67
68= Contributions
69
70== Bash Shell Completion Script
71
72Michael Schuerig contributed a Bash shell completion script. You should copy
73+contrib/piston+ from your gem repository to the appropriate folder. Michael
74said:
75
76 I've put together a bash completion function for piston. On Debian, I
77 just put it in /etc/bash_completion.d, alternatively, the contents can
78 be copied to ~/.bash_completion. I don't know how things are organized
79 on other Unix/Linux systems.
80
81
82= Caveats
83
84== Speed
85
86This tool is SLOW. The update process particularly so. I use a brute force
87approach. Subversion cannot merge from remote repositories, so instead I
88checkout the folder at the initial revision, and then run svn update and
89parse the results of that to determine what changes have occured.
90
91If a local copy of a file was changed, it's changes will be merged back in.
92If that introduces a conflict, Piston will not detect it. The commit will be
93rejected by Subversion anyway.
94
95== Copies / Renames
96
97Piston *does not* track copies. Since Subversion does renames in two
98phases (copy + delete), that is what Piston does.
99
100== Local Operations Only
101
102Piston only works if you have a working copy. It also never commits your
103working copy directly. You are responsible for reviewing the changes and
104applying any pending fixes.
105
106== Remote Repository UUID
107
108Piston caches the remote repository UUID, allowing it to know if the remote
109repos is still the same. Piston refuses to work against a different
110repository than the one we checked out from originally.
111
112
113= Subversion Properties Used
114
115* <tt>piston:uuid</tt>: The remote repository's UUID, which we always confirm
116 before doing any operations.
117* <tt>piston:root</tt>: The repository root URL from which this Piston folder
118 was exported from.
119* <tt>piston:remote-revision</tt>: The <tt>Last Changed Rev</tt> of the remote
120 repository.
121* <tt>piston:local-revision</tt>: The <tt>Last Changed Rev</tt> of the Piston
122 managed folder, to enable us to know if we need to do any merging.
123* <tt>piston:locked</tt>: The revision at which this folder is locked. If
124 this property is set and non-blank, Piston will skip the folder with
125 an appropriate message.
126
127
128= Dependencies
129
130Piston depends on the following libraries:
131
132* yaml
133* uri
134* fileutils
toggle raw diff

Rakefile

 
11require 'rubygems'
2require 'rake'
3require 'rake/clean'
4require 'rake/testtask'
5require 'rake/packagetask'
26require 'rake/gempackagetask'
7require 'rake/rdoctask'
38require 'rake/contrib/rubyforgepublisher'
4require 'spec/rake/spectask'
5require File.join(File.dirname(__FILE__), 'lib', 'piston', 'version')
9require 'fileutils'
10require 'hoe'
11begin
12 require 'spec/rake/spectask'
13rescue LoadError
14 puts 'To use rspec for testing you must install rspec gem:'
15 puts '$ sudo gem install rspec'
16 exit
17end
618
7PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
8PKG_NAME = 'piston'
9PKG_VERSION = Piston::VERSION::STRING + PKG_BUILD
10PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
19include FileUtils
20require File.join(File.dirname(__FILE__), 'lib', 'piston', 'version')
1121
12RELEASE_NAME = "REL #{PKG_VERSION}"
22AUTHOR = 'Francois Beausoleil' # can also be an array of Authors
23EMAIL = "francois@teksol.info"
24DESCRIPTION = "description of gem"
25GEM_NAME = 'piston' # what ppl will type to install your gem
1326
14RUBY_FORGE_PROJECT = "piston"
15RUBY_FORGE_USER = "fbos"
27@config_file = "~/.rubyforge/user-config.yml"
28@config = nil
29def rubyforge_username
30 unless @config
31 begin
32 @config = YAML.load(File.read(File.expand_path(@config_file)))
33 rescue
34 puts <<-EOS
35ERROR: No rubyforge config file found: #{@config_file}"
36Run 'rubyforge setup' to prepare your env for access to Rubyforge
37 - See http://newgem.rubyforge.org/rubyforge.html for more details
38 EOS
39 exit
40 end
41 end
42 @rubyforge_username ||= @config["username"]
43end
1644
17task :default => :specs
45RUBYFORGE_PROJECT = 'piston' # The unix name for your project
46HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
47DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
1848
19# Create compressed packages
20dist_dirs = [ "lib", "spec"]
49NAME = "piston"
50REV = nil
51# UNCOMMENT IF REQUIRED:
52# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
53VERS = Piston::VERSION::STRING + (REV ? ".#{REV}" : "")
54CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
55RDOC_OPTS = ['--quiet', '--title', 'piston documentation',
56 "--opname", "index.html",
57 "--line-numbers",
58 "--main", "README",
59 "--inline-source"]
2160
22spec = Gem::Specification.new do |s|
23 s.name = PKG_NAME
24 s.version = PKG_VERSION
25 s.summary = "Piston is a utility that enables merge tracking of remote repositories."
26 s.description = %q{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.}
61class Hoe
62 def extra_deps
63 @extra_deps.reject { |x| Array(x).first == 'hoe' }
64 end
65end
2766
28 s.bindir = "bin" # Use these for applications.
29 s.executables = ["piston"]
30 s.default_executable = "piston"
67# Generate all the Rake tasks
68# Run 'rake -T' to see list of generated tasks (from gem root directory)
69hoe = Hoe.new(GEM_NAME, VERS) do |p|
70 p.author = AUTHOR
71 p.description = DESCRIPTION
72 p.email = EMAIL
73 p.summary = DESCRIPTION
74 p.url = HOMEPATH
75 p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
76 p.test_globs = ["test/**/test_*.rb"]
77 p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
78
79 # == Optional
80 p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
81 #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
82 #p.spec_extras = {} # A hash of extra values to set in the gemspec.
83end
3184
32 s.files = [ "CHANGELOG", "README", "LICENSE", "Rakefile" ] + FileList["{contrib,bin,spec,lib}/**/*"].to_a
85CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
86PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
87hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
3388
34 s.require_path = 'lib'
35 s.has_rdoc = false
89desc 'Generate website files'
90task :website_generate do
91 Dir['website/**/*.txt'].each do |txt|
92 sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
93 end
94end
3695
37 s.author = "Francois Beausoleil"
38 s.email = "francois@teksol.info"
39 s.homepage = "http://piston.rubyforge.org/"
40 s.rubyforge_project = "piston"
96desc 'Upload website files to rubyforge'
97task :website_upload do
98 host = "#{rubyforge_username}@rubyforge.org"
99 remote_dir = "/var/www/gforge-projects/#{PATH}/"
100 local_dir = 'website'
101 sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
41102end
42103
43Rake::GemPackageTask.new(spec) do |p|
44 p.gem_spec = spec
45 p.need_tar = true
46 p.need_zip = true
104desc 'Generate and upload website files'
105task :website => [:website_generate, :website_upload, :publish_docs]
106
107desc 'Release the website and new gem version'
108task :deploy => [:check_version, :website, :release] do
109 puts "Remember to create SVN tag:"
110 puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
111 "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
112 puts "Suggested comment:"
113 puts "Tagging release #{CHANGES}"
47114end
48115
49desc "Publish the release files to RubyForge."
50task :release => [ :package ] do
51 `rubyforge login`
116desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
117task :local_deploy => [:website_generate, :install_gem]
52118
53 for ext in %w( gem tgz zip )
54 release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
55 puts release_command
56 system(release_command)
119task :check_version do
120 unless ENV['VERSION']
121 puts 'Must pass a VERSION=x.y.z release version'
122 exit
123 end
124 unless ENV['VERSION'] == VERS
125 puts "Please update your version.rb to match the release version, currently #{VERS}"
126 exit
57127 end
58128end
59129
60desc "Run all examples with RCov"
61Spec::Rake::SpecTask.new('specs') do |t|
62 t.spec_files = FileList['specs/**/*.rb']
130desc "Run the specs under spec/"
131Spec::Rake::SpecTask.new do |t|
132 t.libs << File.dirname(__FILE__) + "/lib"
133 t.spec_opts = ['--options', "spec/spec.opts"]
134 t.spec_files = FileList['spec/*_spec.rb']
63135end
136
137desc "Default task is to run specs"
138task :default => :spec
139
toggle raw diff

lib/piston.rb

 
1# Copyright (c) 2006 Francois Beausoleil <francois@teksol.info>
2#
3# Permission is hereby granted, free of charge, to any person obtaining a copy
4# of this software and associated documentation files (the "Software"), to deal
5# in the Software without restriction, including without limitation the rights
6# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7# copies of the Software, and to permit persons to whom the Software is
8# furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice shall be included in
11# all copies or substantial portions of the Software.
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19# THE SOFTWARE.
20
21# $HeadURL$
22# $Id$
23
24require 'yaml'
25require 'uri'
26require 'fileutils'
27
28PISTON_ROOT = File.dirname(__FILE__)
29Dir[File.join(PISTON_ROOT, 'core_ext', '*.rb')].each do |file|
30 require file
31end
32
33require "piston/version"
34require "piston/command"
35require "piston/command_error"
36
37require "transat/parser"
38Dir[File.join(PISTON_ROOT, "piston", "commands", "*.rb")].each do |file|
39 require file.gsub(PISTON_ROOT, "")[1..-4]
40end
41
421module Piston
43 ROOT = "piston:root"
44 UUID = "piston:uuid"
45 REMOTE_REV = "piston:remote-revision"
46 LOCAL_REV = "piston:local-revision"
47 LOCKED = "piston:locked"
482end
493
50PistonCommandLineProcessor = Transat::Parser.new do
51 program_name "Piston"
52 version [Piston::VERSION::STRING]
53
54 option :verbose, :short => :v, :default => true, :message => "Show subversion commands and results as they are executed"
55 option :quiet, :short => :q, :default => false, :message => "Do not output any messages except errors"
56 option :revision, :short => :r, :param_name => "REVISION", :type => :int
57 option :show_updates, :short => :u, :message => "Query the remote repository for out of dateness information"
58 option :lock, :short => :l, :message => "Close down and lock the imported directory from further changes"
59 option :dry_run, :message => "Does not actually execute any commands"
60 option :force, :message => "Force the command to run, even if Piston thinks it would cause a problem"
61
62 command :switch, Piston::Commands::Switch, :valid_options => %w(lock dry_run force revision quiet verbose)
63 command :update, Piston::Commands::Update, :valid_options => %w(lock dry_run force revision quiet verbose)
64 command :import, Piston::Commands::Import, :valid_options => %w(lock dry_run force revision quiet verbose)
65 command :convert, Piston::Commands::Convert, :valid_options => %w(lock verbose dry_run)
66 command :unlock, Piston::Commands::Unlock, :valid_options => %w(force dry_run verbose)
67 command :lock, Piston::Commands::Lock, :valid_options => %w(force dry_run revision verbose)
68 command :status, Piston::Commands::Status, :valid_options => %w(show_updates verbose)
69end
4require 'piston/version'
toggle raw diff

lib/piston/repository.rb

 
1require "piston/subversion_client"
2require "fileutils"
3require "yaml"
4require "uri"
5
6module Piston
7 # Represents a Subversion repository located somewhere in the ether.
8 class Repository
9 include Piston::SubversionClient
10
11 class RepositoryError < RuntimeError; end
12 class DirectoryAlreadyExists < RepositoryError
13 def initialize(dir)
14 super "Directory #{dir.inspect} already exists"
15 end
16 end
17
18 class NoCreateOnRemoteRepositoryUrl < RepositoryError
19 def initialize(url)
20 super "URL #{url.inspect} is remote -- cannot create there"
21 end
22 end
23
24 attr_reader :url
25
26 def initialize(url)
27 case url
28 when String
29 @url = url
30 when URI
31 @url = url.to_s
32 else
33 raise ArgumentError, "Expected either a String or a URI, got a #{url.class.name}"
34 end
35 end
36
37 def local_path
38 @url.to_s["file://".length .. -1]
39 end
40
41 def youngest
42 self.info["Revision"].to_i
43 end
44
45 def uuid
46 self.info["Repository UUID"]
47 end
48
49 def info
50 @info ||= YAML.load(svn(:info, self.url))
51 end
52
53 # Uses svnadmin to create a repository if this repository's is a file: URL.
54 def create!
55 raise NoCreateOnRemoteRepositoryUrl.new(self.url) unless self.url =~ %r{\Afile://}
56 log {"Want to create repository at #{self.local_path.inspect}"}
57 repos_parent_dir = File.expand_path(self.local_path + "/..")
58
59 FileUtils.mkdir_p(repos_parent_dir)
60 raise DirectoryAlreadyExists.new(self.local_path) if File.directory?(self.local_path)
61
62 svnadmin :create, local_path
63 self
64 end
65 end
66end
toggle raw diff

lib/piston/subversion_client.rb

 
1module Piston
2 module SubversionClient
3 def self.included(base)
4 base.send :attr_accessor, :logger
5 end
6
7 protected
8 def svnadmin(*args)
9 run_cmd :svnadmin, *args
10 end
11
12 def svn(*args)
13 run_cmd :svn, *args
14 end
15
16 def log(&block)
17 @logger.debug &block if logger
18 end
19
20 private