piston 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +4 -0
- data/LICENSE +19 -0
- data/README +114 -0
- data/Rakefile +63 -0
- data/bin/piston +12 -0
- data/lib/core_ext/core_ext/range.rb +5 -0
- data/lib/core_ext/core_ext/string.rb +5 -0
- data/lib/piston/command.rb +46 -0
- data/lib/piston/command_error.rb +4 -0
- data/lib/piston/commands/help.rb +44 -0
- data/lib/piston/commands/import.rb +68 -0
- data/lib/piston/commands/lock.rb +38 -0
- data/lib/piston/commands/unlock.rb +34 -0
- data/lib/piston/commands/update.rb +107 -0
- data/lib/piston/ui/command_line.rb +69 -0
- data/lib/piston/version.rb +9 -0
- data/lib/piston.rb +43 -0
- metadata +67 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
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.
|
data/README
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
Piston is a utility that enables merge tracking of remote repositories.
|
2
|
+
This is similar to <tt>svn:externals</tt>, except you have a local copy of
|
3
|
+
the files, which you can modify at will. As long as the changes are
|
4
|
+
mergeable, you should have no problems.
|
5
|
+
|
6
|
+
This tool has a similar purpose than svnmerge.py which you can find in the
|
7
|
+
contrib/client-side folder of the main Subversion repository at
|
8
|
+
http://svn.collab.net/repos/svn/trunk/contrib/client-side/svnmerge.py.
|
9
|
+
The main difference is that Piston is designed to work with remote
|
10
|
+
repositories. Another tool you might want to look at, SVK, situated at
|
11
|
+
http://svk.elixus.org.
|
12
|
+
|
13
|
+
From 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
|
+
|
20
|
+
For this utility, I retain the second meaning, "to exert a force on a fluid
|
21
|
+
inside the cylinder." Piston forces the content of a remote repository
|
22
|
+
location back into our own.
|
23
|
+
|
24
|
+
= Installation
|
25
|
+
|
26
|
+
Nothing could be simpler:
|
27
|
+
|
28
|
+
$ gem install --include-dependencies piston
|
29
|
+
|
30
|
+
|
31
|
+
= Usage
|
32
|
+
|
33
|
+
First, you need to import the remote repository location:
|
34
|
+
|
35
|
+
$ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails
|
36
|
+
Exported r4720 from 'http://dev.rubyonrails.org/svn/rails/trunk' to 'vendor/rails'
|
37
|
+
|
38
|
+
$ svn commit -m "Importing local copy of Rails"
|
39
|
+
|
40
|
+
When you want to get the latest changes from the remote repository location:
|
41
|
+
|
42
|
+
$ piston update vendor/rails
|
43
|
+
Updated 'vendor/rails' to r4720.
|
44
|
+
|
45
|
+
$ svn commit -m "Updates vendor/rails to the latest revision"
|
46
|
+
|
47
|
+
You can prevent a local Piston-managed folder from updating by using the
|
48
|
+
+lock+ subcommand:
|
49
|
+
|
50
|
+
$ piston lock vendor/rails
|
51
|
+
'vendor/rails' locked at r4720.
|
52
|
+
|
53
|
+
When you want to update again, you unlock:
|
54
|
+
|
55
|
+
$ piston unlock vendor/rails
|
56
|
+
'vendor/rails' unlocked.
|
57
|
+
|
58
|
+
|
59
|
+
= Caveats
|
60
|
+
|
61
|
+
== Speed
|
62
|
+
|
63
|
+
This tool is SLOW. The update process particularly so. I use a brute force
|
64
|
+
approach. Subversion cannot merge from remote repositories, so instead I
|
65
|
+
checkout the folder at the initial revision, and then run svn update and
|
66
|
+
parse the results of that to determine what changes have occured.
|
67
|
+
|
68
|
+
If a local copy of a file was changed, it's changes will be merged back in.
|
69
|
+
If that introduces a conflict, Piston will not detect it. The commit will be
|
70
|
+
rejected by Subversion anyway.
|
71
|
+
|
72
|
+
== Copies / Renames
|
73
|
+
|
74
|
+
Piston *does not* track copies. Since Subversion does renames in two
|
75
|
+
phases (copy + delete), that is what Piston does.
|
76
|
+
|
77
|
+
== Local Operations Only
|
78
|
+
|
79
|
+
Piston only works if you have a working copy. It also never commits your
|
80
|
+
working copy directly. You are responsible for reviewing the changes and
|
81
|
+
applying any pending fixes.
|
82
|
+
|
83
|
+
== Remote Repository UUID
|
84
|
+
|
85
|
+
Piston caches the remote repository UUID, allowing it to know if the remote
|
86
|
+
repos is still the same. Piston refuses to work against a different
|
87
|
+
repository than the one we checked out from originally.
|
88
|
+
|
89
|
+
|
90
|
+
= Subversion Properties Used
|
91
|
+
|
92
|
+
* <tt>piston:uuid</tt>: The remote repository's UUID, which we always confirm
|
93
|
+
before doing any operations.
|
94
|
+
* <tt>piston:root</tt>: The repository root URL from which this Piston folder
|
95
|
+
was exported from.
|
96
|
+
* <tt>piston:remote-revision</tt>: The <tt>Last Changed Rev</tt> of the remote
|
97
|
+
repository.
|
98
|
+
* <tt>piston:local-revision</tt>: The <tt>Last Changed Rev</tt> of the Piston
|
99
|
+
managed folder, to enable us to know if we need to do any merging.
|
100
|
+
* <tt>piston:locked</tt>: The revision at which this folder is locked. If
|
101
|
+
this property is set and non-blank, Piston will skip the folder with
|
102
|
+
an appropriate message.
|
103
|
+
|
104
|
+
|
105
|
+
= Dependencies
|
106
|
+
|
107
|
+
Piston depends on the following libraries:
|
108
|
+
|
109
|
+
* yaml
|
110
|
+
* getoptlong
|
111
|
+
* uri
|
112
|
+
* fileutils
|
113
|
+
|
114
|
+
These dependencies are all included in a stock 1.8.4 Ruby distribution.
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/rubyforgepublisher'
|
5
|
+
require File.join(File.dirname(__FILE__), 'lib', 'piston', 'version')
|
6
|
+
|
7
|
+
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
8
|
+
PKG_NAME = 'piston'
|
9
|
+
PKG_VERSION = Piston::VERSION::STRING + PKG_BUILD
|
10
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
11
|
+
|
12
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
13
|
+
|
14
|
+
RUBY_FORGE_PROJECT = "piston"
|
15
|
+
RUBY_FORGE_USER = "fbos"
|
16
|
+
|
17
|
+
task :default => :test
|
18
|
+
Rake::TestTask.new { |t|
|
19
|
+
t.pattern = 'test/**/*_test.rb'
|
20
|
+
t.verbose = true
|
21
|
+
t.warning = false
|
22
|
+
}
|
23
|
+
|
24
|
+
# Create compressed packages
|
25
|
+
dist_dirs = [ "lib", "test"]
|
26
|
+
|
27
|
+
spec = Gem::Specification.new do |s|
|
28
|
+
s.name = PKG_NAME
|
29
|
+
s.version = PKG_VERSION
|
30
|
+
s.summary = "Piston is a utility that enables merge tracking of remote repositories."
|
31
|
+
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.}
|
32
|
+
|
33
|
+
s.bindir = "bin" # Use these for applications.
|
34
|
+
s.executables = ["piston"]
|
35
|
+
s.default_executable = "piston"
|
36
|
+
|
37
|
+
s.files = [ "CHANGELOG", "README", "LICENSE", "Rakefile" ] + FileList["{bin,test,lib}/**/*"].to_a
|
38
|
+
|
39
|
+
s.require_path = 'lib'
|
40
|
+
s.has_rdoc = false
|
41
|
+
|
42
|
+
s.author = "Francois Beausoleil"
|
43
|
+
s.email = "francois@teksol.info"
|
44
|
+
s.homepage = "http://piston.rubyforge.org/"
|
45
|
+
s.rubyforge_project = "piston"
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::GemPackageTask.new(spec) do |p|
|
49
|
+
p.gem_spec = spec
|
50
|
+
p.need_tar = true
|
51
|
+
p.need_zip = true
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Publish the release files to RubyForge."
|
55
|
+
task :release => [ :package ] do
|
56
|
+
`rubyforge login`
|
57
|
+
|
58
|
+
for ext in %w( gem tgz zip )
|
59
|
+
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
|
60
|
+
puts release_command
|
61
|
+
system(release_command)
|
62
|
+
end
|
63
|
+
end
|
data/bin/piston
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Piston
|
2
|
+
# The base class which all commands subclass to obtain services from.
|
3
|
+
class Command
|
4
|
+
attr_accessor :revision, :dry_run, :quiet, :verbose, :force, :lock,
|
5
|
+
:logging_stream
|
6
|
+
|
7
|
+
# Execute this command. The arguments are pre-processed to expand any
|
8
|
+
# wildcards using Dir#[]. This is because the Windows shell does not
|
9
|
+
# know it should expand wildcards before calling an executable.
|
10
|
+
def execute(args)
|
11
|
+
# Because the Windows shell does not process wildcards, we must do it
|
12
|
+
# here ourselves
|
13
|
+
args.map do |arg|
|
14
|
+
next arg unless arg =~ /[*?]/
|
15
|
+
Dir[arg]
|
16
|
+
end
|
17
|
+
|
18
|
+
run(args.flatten)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Run a Subversion command using the shell. If the Subversion command
|
22
|
+
# returns an existstatus different from zero, a RuntimeError is raised.
|
23
|
+
def svn(*args)
|
24
|
+
args = args.flatten.compact.map do |arg|
|
25
|
+
if arg.to_s =~ /[ *?@]/ then
|
26
|
+
%Q("#{arg}")
|
27
|
+
else
|
28
|
+
arg
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
command = "svn #{args.join(' ')}"
|
33
|
+
logging_stream.puts command if verbose
|
34
|
+
return if dry_run
|
35
|
+
result = `#{command}`
|
36
|
+
logging_stream.puts result if verbose
|
37
|
+
raise "Command #{command} resulted in an error:\n\n#{result}" unless $?.exitstatus.zero?
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an IO-like object to which all information should be logged.
|
42
|
+
def logging_stream
|
43
|
+
@logging_stream ||= $stdout
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Piston
|
2
|
+
module Commands
|
3
|
+
class Help < Piston::Command
|
4
|
+
def run(targets=nil)
|
5
|
+
command = targets.shift
|
6
|
+
|
7
|
+
return help_on_command(command) if command
|
8
|
+
general_help
|
9
|
+
end
|
10
|
+
|
11
|
+
def help_on_command(command_name)
|
12
|
+
begin
|
13
|
+
require File.join(PISTON_ROOT, 'piston', 'commands', command_name)
|
14
|
+
command = Piston::Commands.const_get(command_name.capitalize)
|
15
|
+
command.detailed_help(logging_stream)
|
16
|
+
rescue LoadError
|
17
|
+
logging_stream.puts "No help available for '#{command_name}'"
|
18
|
+
general_help
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def general_help
|
23
|
+
logging_stream.puts "Available commands are:"
|
24
|
+
commands = Array.new
|
25
|
+
Dir[File.join(PISTON_ROOT, 'piston', 'commands', '*.rb')].each do |file|
|
26
|
+
require file
|
27
|
+
commands << Piston::Commands.const_get(File.basename(file).gsub(/\.rb$/, '').capitalize)
|
28
|
+
end
|
29
|
+
|
30
|
+
commands.each do |command|
|
31
|
+
logging_stream.printf " %-12s %s\n", command.aliases.first, command.help
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.help
|
36
|
+
"Returns detailed help on a specific command"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.aliases
|
40
|
+
%w(help)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Piston
|
2
|
+
module Commands
|
3
|
+
class Import < Piston::Command
|
4
|
+
def run(args)
|
5
|
+
raise Piston::CommandError, "Missing REPOS_URL argument" if args.empty?
|
6
|
+
|
7
|
+
repos, dir = args.shift, args.shift
|
8
|
+
raise Piston::CommandError, "Too many arguments" unless args.empty?
|
9
|
+
dir = File.basename(URI.parse(repos).path) unless dir
|
10
|
+
|
11
|
+
if File.exists?(dir) then
|
12
|
+
raise Piston::CommandError, "Target folder already exists" unless force
|
13
|
+
svn :revert, '--recursive', dir
|
14
|
+
FileUtils.rm_rf(dir)
|
15
|
+
end
|
16
|
+
|
17
|
+
info = YAML::load(svn(:info, repos))
|
18
|
+
options = [:export]
|
19
|
+
options << ['--revision', revision] if revision
|
20
|
+
options << repos
|
21
|
+
options << dir
|
22
|
+
export = svn options
|
23
|
+
export.each_line do |line|
|
24
|
+
next unless line =~ /Exported revision (\d+)./i
|
25
|
+
@revision = $1
|
26
|
+
break
|
27
|
+
end
|
28
|
+
|
29
|
+
svn :add, '--force', dir
|
30
|
+
svn :propset, Piston::ROOT, repos, dir
|
31
|
+
svn :propset, Piston::UUID, info['Repository UUID'], dir
|
32
|
+
svn :propset, Piston::REMOTE_REV, revision, dir
|
33
|
+
svn :propset, Piston::LOCAL_REV, YAML::load(svn(:info))['Last Changed Rev'], dir
|
34
|
+
svn :propset, Piston::LOCKED, revision, dir if lock
|
35
|
+
|
36
|
+
logging_stream.puts "Exported r#{revision} from '#{repos}' to '#{dir}'"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.help
|
40
|
+
"Prepares a folder for merge tracking"
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.detailed_help(stream)
|
44
|
+
stream.puts <<EOF
|
45
|
+
import (init): #{help}
|
46
|
+
usage: import REPOS_URL [DIR]
|
47
|
+
|
48
|
+
Exports the specified REPOS_URL (which must be a Subversion repository) to
|
49
|
+
DIR, defaulting to the last component of REPOS_URL if DIR is not present.
|
50
|
+
|
51
|
+
If the local folder already exists, this command will abort with an error.
|
52
|
+
|
53
|
+
Valid options:
|
54
|
+
-r [--revision] arg : Start merge tracking at ARG instead of HEAD
|
55
|
+
--lock : Close down and lock the folder from future
|
56
|
+
updates immediately
|
57
|
+
--verbose : Show Subversion commands and results as they
|
58
|
+
are executed
|
59
|
+
|
60
|
+
EOF
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.aliases
|
64
|
+
%w(import init)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Piston
|
2
|
+
module Commands
|
3
|
+
class Lock < Piston::Command
|
4
|
+
def run(args)
|
5
|
+
raise Piston::CommandError, "No targets to run against" if args.empty?
|
6
|
+
|
7
|
+
args.each do |dir|
|
8
|
+
remote_rev = svn(:propget, Piston::REMOTE_REV, dir).chomp.to_i
|
9
|
+
svn :propset, Piston::LOCKED, remote_rev, dir
|
10
|
+
end
|
11
|
+
|
12
|
+
logging_stream.puts "Locked #{args.size} folder(s)"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.help
|
16
|
+
"Lock one or more folders to a specific revision"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.detailed_help(stream)
|
20
|
+
stream.puts <<EOF
|
21
|
+
lock: #{help}
|
22
|
+
usage: lock DIR [DIR [...]]
|
23
|
+
|
24
|
+
Locked folders will not be updated to the latest revision when updating.
|
25
|
+
|
26
|
+
Valid options:
|
27
|
+
--verbose : Show Subversion commands and results as they
|
28
|
+
are executed
|
29
|
+
|
30
|
+
EOF
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.aliases
|
34
|
+
%w(lock)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Piston
|
2
|
+
module Commands
|
3
|
+
class Unlock < Piston::Command
|
4
|
+
def run(args)
|
5
|
+
raise Piston::CommandError, "No targets to run against" if args.empty?
|
6
|
+
svn :propdel, Piston::LOCKED, *args
|
7
|
+
logging_stream.puts "Unlocked #{args.size} folder(s)"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.help
|
11
|
+
"Undoes the changes enabled by lock"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.detailed_help(stream)
|
15
|
+
stream.puts <<EOF
|
16
|
+
unlock: #{help}
|
17
|
+
usage: unlock DIR [DIR [...]]
|
18
|
+
|
19
|
+
Unlocked folders are free to be updated to the latest revision when
|
20
|
+
updating.
|
21
|
+
|
22
|
+
Valid options:
|
23
|
+
--verbose : Show Subversion commands and results as they
|
24
|
+
are executed
|
25
|
+
|
26
|
+
EOF
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.aliases
|
30
|
+
%w(unlock)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Piston
|
2
|
+
module Commands
|
3
|
+
class Update < Piston::Command
|
4
|
+
def run(args)
|
5
|
+
args.each do |dir|
|
6
|
+
update dir
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def update(dir)
|
11
|
+
next unless File.directory?(dir)
|
12
|
+
next skip(dir, "locked") unless svn(:propget, LOCKED, dir) == ''
|
13
|
+
status = svn :status, '--show-updates', dir
|
14
|
+
next skip(dir, "pending updates -- run \"svn update #{dir}\"") if status.split("\n").size > 1
|
15
|
+
|
16
|
+
logging_stream.print "Processing '#{dir}'... "
|
17
|
+
repos = svn(:propget, Piston::ROOT, dir).chomp
|
18
|
+
uuid = svn(:propget, Piston::UUID, dir).chomp
|
19
|
+
remote_revision = svn(:propget, Piston::REMOTE_REV, dir).chomp.to_i
|
20
|
+
local_revision = svn(:propget, Piston::LOCAL_REV, dir).chomp.to_i
|
21
|
+
|
22
|
+
info = YAML::load(svn(:info, repos))
|
23
|
+
next skip(dir, "Repository UUID changed\n Expected #{uuid}\n Found #{info['Repository UUID']}\n Repository: #{repos}") unless uuid == info['Repository UUID']
|
24
|
+
|
25
|
+
new_remote_rev = info['Last Changed Rev'].to_i
|
26
|
+
next skip(dir, "unchanged from revision #{remote_revision}") if remote_revision == new_remote_rev
|
27
|
+
|
28
|
+
info = YAML::load(svn(:info))
|
29
|
+
new_local_rev = info['Last Changed Rev']
|
30
|
+
|
31
|
+
revisions = (remote_revision .. (revision || new_remote_rev))
|
32
|
+
svn :checkout, '--ignore-externals', '--revision', revisions.first, repos, dir.tmp
|
33
|
+
updates = svn :update, '--ignore-externals', '--revision', revisions.last, dir.tmp
|
34
|
+
merges = Array.new
|
35
|
+
changes = 0
|
36
|
+
updates.each_line do |line|
|
37
|
+
next unless line =~ %r{^([A-Z]).*\s+#{Regexp.escape(dir.tmp)}[\\/](.+)$}
|
38
|
+
op, file = $1, $2
|
39
|
+
changes += 1
|
40
|
+
|
41
|
+
case op
|
42
|
+
when 'A'
|
43
|
+
copy(dir, file)
|
44
|
+
svn :add, File.join(dir, file)
|
45
|
+
when 'D'
|
46
|
+
svn :remove, File.join(dir, file)
|
47
|
+
else
|
48
|
+
copy(dir, file)
|
49
|
+
merges << file
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
merges.each do |file|
|
54
|
+
svn :merge, '--revision', (local_revision.succ .. new_local_rev).to_svn, File.join(dir, file), File.join(dir, file)
|
55
|
+
end unless local_revision == new_local_rev
|
56
|
+
|
57
|
+
FileUtils.rm_rf dir.tmp
|
58
|
+
|
59
|
+
svn :propset, Piston::REMOTE_REV, revisions.last, dir
|
60
|
+
svn :propset, Piston::LOCAL_REV, new_local_rev, dir
|
61
|
+
svn :propset, Piston::LOCKED, revisions.last, dir if lock
|
62
|
+
|
63
|
+
logging_stream.puts "Updated to #{revisions.last} (#{changes} changed files)"
|
64
|
+
end
|
65
|
+
|
66
|
+
def copy(dir, file)
|
67
|
+
FileUtils.cp(File.join(dir.tmp, file), File.join(dir, file))
|
68
|
+
end
|
69
|
+
|
70
|
+
def skip(dir, msg)
|
71
|
+
logging_stream.puts "Skipping '#{dir}': #{msg}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.help
|
75
|
+
"Updates one or more folders to the latest revision"
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.detailed_help(stream)
|
79
|
+
stream.puts <<EOF
|
80
|
+
update: #{help}
|
81
|
+
usage: update DIR [...]
|
82
|
+
|
83
|
+
This operation has the effect of downloading all remote changes back to our
|
84
|
+
working copy. If any local modifications were done, they will be preserved.
|
85
|
+
If merge conflicts occur, they will not be taken care of, and your subsequent
|
86
|
+
commit will fail.
|
87
|
+
|
88
|
+
Piston will refuse to update a folder if it has pending updates. Run
|
89
|
+
'svn update' on the target folder to update it before running Piston
|
90
|
+
again.
|
91
|
+
|
92
|
+
Valid options:
|
93
|
+
-r [--revision] arg : Update to ARG instead of HEAD
|
94
|
+
--lock : Close down and lock the folder from future
|
95
|
+
updates immediately
|
96
|
+
--verbose : Show Subversion commands and results as they
|
97
|
+
are executed
|
98
|
+
|
99
|
+
EOF
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.aliases
|
103
|
+
%w(update up)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'getoptlong'
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Ui
|
5
|
+
module CommandLine
|
6
|
+
def self.start
|
7
|
+
opts = ::GetoptLong.new(
|
8
|
+
[ '--revision', '-r', GetoptLong::REQUIRED_ARGUMENT ],
|
9
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
10
|
+
[ '--dry-run', GetoptLong::NO_ARGUMENT ],
|
11
|
+
[ '--quiet', '-q', GetoptLong::NO_ARGUMENT ],
|
12
|
+
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
|
13
|
+
[ '--lock', GetoptLong::NO_ARGUMENT ],
|
14
|
+
[ '--force', '-f', GetoptLong::NO_ARGUMENT ],
|
15
|
+
[ '--version', GetoptLong::NO_ARGUMENT ]
|
16
|
+
)
|
17
|
+
|
18
|
+
options = Hash.new
|
19
|
+
|
20
|
+
opts.each do |opt, arg|
|
21
|
+
case opt
|
22
|
+
when '--revision'
|
23
|
+
options[:revision] = arg.to_i
|
24
|
+
|
25
|
+
when '--help'
|
26
|
+
return help
|
27
|
+
|
28
|
+
when '--version'
|
29
|
+
require 'piston/version'
|
30
|
+
puts "Piston #{Piston::VERSION::STRING}"
|
31
|
+
puts "Copyright 2006, Francois Beausoleil"
|
32
|
+
puts "\nSee the LICENSE file for details"
|
33
|
+
exit
|
34
|
+
|
35
|
+
when /--([-\w]+)$/
|
36
|
+
options[$1.gsub('-', '_').to_sym] = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return help if ARGV.empty?
|
41
|
+
|
42
|
+
command_name = ARGV.shift.downcase
|
43
|
+
begin
|
44
|
+
require File.join(PISTON_ROOT, 'piston', 'commands', command_name)
|
45
|
+
rescue LoadError
|
46
|
+
return help
|
47
|
+
end
|
48
|
+
|
49
|
+
command_class = Piston::Commands.const_get("#{command_name.capitalize}")
|
50
|
+
command = command_class.new
|
51
|
+
options.each do |key, value|
|
52
|
+
command.send "#{key}=", value
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
command.execute(ARGV)
|
57
|
+
rescue Piston::CommandError
|
58
|
+
$stderr.puts "ERROR: #{$!.message}"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.help
|
64
|
+
require File.join(PISTON_ROOT, 'piston', 'commands', 'help')
|
65
|
+
Piston::Commands::Help.new.run(ARGV)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/piston.rb
ADDED
@@ -0,0 +1,43 @@
|
|
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: svn://rubyforge.org/var/svn/piston/tags/1.0.0/lib/piston.rb $
|
22
|
+
# $Id: piston.rb 11 2006-08-25 02:47:12Z fbos $
|
23
|
+
|
24
|
+
require 'yaml'
|
25
|
+
require 'uri'
|
26
|
+
require 'fileutils'
|
27
|
+
|
28
|
+
PISTON_ROOT = File.dirname(__FILE__)
|
29
|
+
Dir[File.join(PISTON_ROOT, 'core_ext', '*.rb')].each do |file|
|
30
|
+
require file
|
31
|
+
end
|
32
|
+
|
33
|
+
require File.join(PISTON_ROOT, 'piston', 'command')
|
34
|
+
require File.join(PISTON_ROOT, 'piston', 'command_error')
|
35
|
+
require File.join(PISTON_ROOT, 'piston', 'ui', 'command_line')
|
36
|
+
|
37
|
+
module Piston
|
38
|
+
ROOT = "piston:root"
|
39
|
+
UUID = "piston:uuid"
|
40
|
+
REMOTE_REV = "piston:remote-revision"
|
41
|
+
LOCAL_REV = "piston:local-revision"
|
42
|
+
LOCKED = "piston:locked"
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: piston
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2006-08-25 00:00:00 +00:00
|
8
|
+
summary: Piston is a utility that enables merge tracking of remote repositories.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: francois@teksol.info
|
12
|
+
homepage: http://piston.rubyforge.org/
|
13
|
+
rubyforge_project: piston
|
14
|
+
description: 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.
|
15
|
+
autorequire:
|
16
|
+
default_executable: piston
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Francois Beausoleil
|
31
|
+
files:
|
32
|
+
- CHANGELOG
|
33
|
+
- README
|
34
|
+
- LICENSE
|
35
|
+
- Rakefile
|
36
|
+
- bin/piston
|
37
|
+
- lib/core_ext
|
38
|
+
- lib/piston
|
39
|
+
- lib/piston.rb
|
40
|
+
- lib/core_ext/core_ext
|
41
|
+
- lib/core_ext/core_ext/string.rb
|
42
|
+
- lib/core_ext/core_ext/range.rb
|
43
|
+
- lib/piston/commands
|
44
|
+
- lib/piston/ui
|
45
|
+
- lib/piston/command_error.rb
|
46
|
+
- lib/piston/command.rb
|
47
|
+
- lib/piston/version.rb
|
48
|
+
- lib/piston/commands/update.rb
|
49
|
+
- lib/piston/commands/help.rb
|
50
|
+
- lib/piston/commands/lock.rb
|
51
|
+
- lib/piston/commands/import.rb
|
52
|
+
- lib/piston/commands/unlock.rb
|
53
|
+
- lib/piston/ui/command_line.rb
|
54
|
+
test_files: []
|
55
|
+
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
executables:
|
61
|
+
- piston
|
62
|
+
extensions: []
|
63
|
+
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
dependencies: []
|
67
|
+
|