francois-piston 2.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/History.txt +19 -0
- data/License.txt +20 -0
- data/Manifest.txt +109 -0
- data/README.txt +136 -0
- data/VERSION.yml +4 -0
- data/bin/piston +5 -0
- data/lib/piston.rb +18 -0
- data/lib/piston/cli.rb +391 -0
- data/lib/piston/commands.rb +4 -0
- data/lib/piston/commands/base.rb +44 -0
- data/lib/piston/commands/convert.rb +26 -0
- data/lib/piston/commands/diff.rb +12 -0
- data/lib/piston/commands/import.rb +43 -0
- data/lib/piston/commands/info.rb +14 -0
- data/lib/piston/commands/lock_unlock.rb +21 -0
- data/lib/piston/commands/status.rb +40 -0
- data/lib/piston/commands/update.rb +34 -0
- data/lib/piston/commands/upgrade.rb +20 -0
- data/lib/piston/git.rb +13 -0
- data/lib/piston/git/client.rb +76 -0
- data/lib/piston/git/commit.rb +114 -0
- data/lib/piston/git/repository.rb +63 -0
- data/lib/piston/git/working_copy.rb +142 -0
- data/lib/piston/repository.rb +61 -0
- data/lib/piston/revision.rb +83 -0
- data/lib/piston/svn.rb +15 -0
- data/lib/piston/svn/client.rb +88 -0
- data/lib/piston/svn/repository.rb +67 -0
- data/lib/piston/svn/revision.rb +112 -0
- data/lib/piston/svn/working_copy.rb +182 -0
- data/lib/piston/version.rb +9 -0
- data/lib/piston/working_copy.rb +334 -0
- data/lib/subclass_responsibility_error.rb +2 -0
- data/test/integration_helpers.rb +35 -0
- data/test/spec_suite.rb +4 -0
- data/test/test_helper.rb +83 -0
- data/test/unit/git/commit/test_checkout.rb +31 -0
- data/test/unit/git/commit/test_each.rb +30 -0
- data/test/unit/git/commit/test_rememberance.rb +22 -0
- data/test/unit/git/commit/test_validation.rb +34 -0
- data/test/unit/git/repository/test_at.rb +23 -0
- data/test/unit/git/repository/test_basename.rb +12 -0
- data/test/unit/git/repository/test_branchanme.rb +15 -0
- data/test/unit/git/repository/test_guessing.rb +32 -0
- data/test/unit/git/working_copy/test_copying.rb +25 -0
- data/test/unit/git/working_copy/test_creation.rb +22 -0
- data/test/unit/git/working_copy/test_existence.rb +18 -0
- data/test/unit/git/working_copy/test_finalization.rb +15 -0
- data/test/unit/git/working_copy/test_guessing.rb +35 -0
- data/test/unit/git/working_copy/test_rememberance.rb +22 -0
- data/test/unit/svn/repository/test_at.rb +19 -0
- data/test/unit/svn/repository/test_basename.rb +24 -0
- data/test/unit/svn/repository/test_guessing.rb +45 -0
- data/test/unit/svn/revision/test_checkout.rb +28 -0
- data/test/unit/svn/revision/test_each.rb +22 -0
- data/test/unit/svn/revision/test_rememberance.rb +38 -0
- data/test/unit/svn/revision/test_validation.rb +50 -0
- data/test/unit/svn/working_copy/test_copying.rb +26 -0
- data/test/unit/svn/working_copy/test_creation.rb +16 -0
- data/test/unit/svn/working_copy/test_existence.rb +23 -0
- data/test/unit/svn/working_copy/test_externals.rb +56 -0
- data/test/unit/svn/working_copy/test_finalization.rb +17 -0
- data/test/unit/svn/working_copy/test_guessing.rb +18 -0
- data/test/unit/svn/working_copy/test_rememberance.rb +26 -0
- data/test/unit/test_info.rb +37 -0
- data/test/unit/test_lock_unlock.rb +47 -0
- data/test/unit/test_repository.rb +51 -0
- data/test/unit/test_revision.rb +31 -0
- data/test/unit/working_copy/test_guessing.rb +35 -0
- data/test/unit/working_copy/test_info.rb +14 -0
- data/test/unit/working_copy/test_rememberance.rb +42 -0
- data/test/unit/working_copy/test_validate.rb +63 -0
- metadata +178 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module Piston
|
2
|
+
module Commands
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def logger
|
6
|
+
@@logger ||= Log4r::Logger["main"]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :options
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
@options = options
|
14
|
+
logger.debug {"#{self.class.name} with options #{options.inspect}"}
|
15
|
+
end
|
16
|
+
|
17
|
+
def verbose
|
18
|
+
@options[:verbose]
|
19
|
+
end
|
20
|
+
|
21
|
+
def force
|
22
|
+
@options[:force]
|
23
|
+
end
|
24
|
+
|
25
|
+
def quiet
|
26
|
+
@options[:quiet]
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
self.class.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def guess_wc(wcdir)
|
34
|
+
Piston::WorkingCopy.guess(wcdir)
|
35
|
+
end
|
36
|
+
|
37
|
+
def working_copy!(wcdir)
|
38
|
+
wc = guess_wc(wcdir)
|
39
|
+
wc.validate!
|
40
|
+
wc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "piston/commands/base"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Commands
|
5
|
+
class Convert < Piston::Commands::Base
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def run(targets)
|
9
|
+
targets = Array.new unless targets
|
10
|
+
wc = Piston::Svn::WorkingCopy.new(Dir.pwd)
|
11
|
+
importer = Piston::Commands::Import.new(options)
|
12
|
+
returning(Array.new) do |conversions|
|
13
|
+
wc.externals.each_pair do |dir, args|
|
14
|
+
next unless targets.empty? || targets.detect {|target| dir.to_s.include?(target.to_s) }
|
15
|
+
conversions << dir
|
16
|
+
|
17
|
+
logger.info "Importing #{dir.relative_path_from(wc.path)} from #{args[:url]}"
|
18
|
+
importer.run(args[:url], args[:revision], dir)
|
19
|
+
end
|
20
|
+
|
21
|
+
wc.remove_external_references(*targets)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "piston/commands/base"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Commands
|
5
|
+
class Import < Piston::Commands::Base
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def repository_type
|
9
|
+
options[:repository_type]
|
10
|
+
end
|
11
|
+
|
12
|
+
def select_repository(repository_url)
|
13
|
+
if repository_type then
|
14
|
+
logger.info {"Forced repository type to #{repository_type}"}
|
15
|
+
repository_class_name = "Piston::#{repository_type.downcase.capitalize}::Repository"
|
16
|
+
repository_class = repository_class_name.constantize
|
17
|
+
repository_class.new(repository_url)
|
18
|
+
else
|
19
|
+
logger.info {"Guessing the repository type"}
|
20
|
+
Piston::Repository.guess(repository_url)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(repository_url, target_revision, wcdir)
|
25
|
+
repository = select_repository(repository_url)
|
26
|
+
revision = repository.at(target_revision)
|
27
|
+
|
28
|
+
wcdir = File.expand_path(wcdir.nil? ? repository.basename : wcdir)
|
29
|
+
logger.info {"Guessing the working copy type"}
|
30
|
+
logger.debug {"repository_url: #{repository_url.inspect}, target_revision: #{target_revision.inspect}, wcdir: #{wcdir.inspect}"}
|
31
|
+
working_copy = guess_wc(wcdir)
|
32
|
+
|
33
|
+
if working_copy.exist? && !force then
|
34
|
+
logger.fatal "Path #{working_copy} already exists and --force not given. Aborting..."
|
35
|
+
abort
|
36
|
+
end
|
37
|
+
|
38
|
+
working_copy.import(revision, options[:lock])
|
39
|
+
logger.info {"Imported #{revision} from #{repository}"}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "piston/commands/base"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Commands
|
5
|
+
class LockUnlock < Piston::Commands::Base
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def run(lock)
|
9
|
+
working_copy = working_copy!(File.expand_path(options[:wcdir]))
|
10
|
+
|
11
|
+
values = working_copy.recall
|
12
|
+
values["lock"] = lock
|
13
|
+
working_copy.remember(values, values["handler"])
|
14
|
+
working_copy.finalize
|
15
|
+
|
16
|
+
text = lock ? "Locked" : "Unlocked"
|
17
|
+
logger.info "#{text} #{working_copy} against automatic updates"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "piston/commands/base"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Commands
|
5
|
+
class Status < Piston::Commands::Base
|
6
|
+
def run(wcdir)
|
7
|
+
# Get the working copy handler to search pistonized folders inside it
|
8
|
+
handler = guess_wc(wcdir).class
|
9
|
+
|
10
|
+
# First, find the list of pistonized folders
|
11
|
+
repos = Hash.new
|
12
|
+
Pathname.glob(wcdir + '**/.piston.yml') do |path|
|
13
|
+
repos[path.dirname] = Hash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Then, get their properties
|
17
|
+
repos.each_pair do |path, props|
|
18
|
+
logger.debug {"Get info of #{path}"}
|
19
|
+
working_copy = handler.new(path)
|
20
|
+
working_copy.validate!
|
21
|
+
props.update(working_copy.info)
|
22
|
+
props[:locally_modified] = 'M' if working_copy.locally_modified
|
23
|
+
props[:remotely_modified] = 'M' if show_updates and working_copy.remotely_modified
|
24
|
+
end
|
25
|
+
|
26
|
+
# Display the results
|
27
|
+
repos.each_pair do |path, props|
|
28
|
+
printf "%1s%1s %6s %s (%s)\n", props[:locally_modified],
|
29
|
+
props[:remotely_modified], props["lock"] ? 'locked' : '', path, props["repository_url"]
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "No pistonized folders found in #{wcdir}" if repos.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def show_updates
|
36
|
+
options[:show_updates]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "piston/commands/base"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Commands
|
5
|
+
class Update < Piston::Commands::Base
|
6
|
+
# +wcdir+ is the working copy we're going to change.
|
7
|
+
# +to+ is the new target revision we want to be at after update returns.
|
8
|
+
def run(wcdir, to)
|
9
|
+
working_copy = working_copy!(wcdir)
|
10
|
+
|
11
|
+
logger.debug {"Recalling previously saved values"}
|
12
|
+
values = working_copy.recall
|
13
|
+
return "#{wcdir} is locked: not updating" if values["lock"]
|
14
|
+
|
15
|
+
repository = working_copy.repository
|
16
|
+
from_revision = repository.at(values["handler"])
|
17
|
+
to_revision = repository.at(to)
|
18
|
+
to_revision.resolve!
|
19
|
+
|
20
|
+
logger.debug {"Validating that #{from_revision} exists and is capable of performing the update"}
|
21
|
+
from_revision.validate!
|
22
|
+
|
23
|
+
logger.info {"Updating from #{from_revision} to #{to_revision}"}
|
24
|
+
|
25
|
+
changed = working_copy.update(from_revision, to_revision, options[:lock])
|
26
|
+
if changed then
|
27
|
+
logger.info {"Updated #{wcdir} to #{to_revision}"}
|
28
|
+
else
|
29
|
+
logger.info {"Upstream #{repository} was unchanged from #{from_revision}"}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "piston/commands/base"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Commands
|
5
|
+
class Upgrade < Piston::Commands::Base
|
6
|
+
def run(*directories)
|
7
|
+
# piston 1.x managed only subversion repositories
|
8
|
+
directories = directories.select { |dir| Piston::Svn::WorkingCopy.understands_dir? dir }
|
9
|
+
|
10
|
+
repositories = Piston::Svn::WorkingCopy.old_repositories(*directories)
|
11
|
+
repositories.each do |repository|
|
12
|
+
logger.debug {"Upgrading repository #{repository}"}
|
13
|
+
Piston::Svn::WorkingCopy.new(repository).upgrade
|
14
|
+
end
|
15
|
+
|
16
|
+
repositories
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/piston/git.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Git
|
5
|
+
class Client
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
class CommandError < RuntimeError; end
|
9
|
+
class Failed < CommandError; end
|
10
|
+
class BadCommand < CommandError; end
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger ||= Log4r::Logger["handler::client"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def out_logger
|
17
|
+
@out_logger ||= Log4r::Logger["handler::client::out"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def git(*args)
|
21
|
+
run_cmd :git, *args
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def run_cmd(executable, *args)
|
26
|
+
args.collect! {|arg| arg.to_s =~ /\s|\*|\?|"|\n|\r/ ? %Q('#{arg}') : arg}
|
27
|
+
args.collect! {|arg| arg ? arg : '""'}
|
28
|
+
cmd = %Q|#{executable} #{args.join(' ')}|
|
29
|
+
logger.debug {"> " + cmd}
|
30
|
+
|
31
|
+
original_language = ENV["LANGUAGE"]
|
32
|
+
begin
|
33
|
+
ENV["LANGUAGE"] = "C"
|
34
|
+
value = run_real(cmd)
|
35
|
+
out_logger.info {"< " + value} unless (value || "").strip.empty?
|
36
|
+
return value
|
37
|
+
ensure
|
38
|
+
ENV["LANGUAGE"] = original_language
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
raise LoadError, "Not implemented on Win32 machines" if RUBY_PLATFORM =~ /mswin32/
|
44
|
+
|
45
|
+
begin
|
46
|
+
require "rubygems"
|
47
|
+
rescue LoadError
|
48
|
+
# NOP -- attempt to load without Rubygems
|
49
|
+
end
|
50
|
+
|
51
|
+
require "open4"
|
52
|
+
|
53
|
+
def run_real(cmd)
|
54
|
+
begin
|
55
|
+
pid, stdin, stdout, stderr = Open4::popen4(cmd)
|
56
|
+
_, cmdstatus = Process.waitpid2(pid)
|
57
|
+
raise CommandError, "#{cmd.inspect} exited with status: #{cmdstatus.exitstatus}\n#{stderr.read}" unless cmdstatus.success? || cmdstatus.exitstatus == 1
|
58
|
+
return stdout.read
|
59
|
+
rescue Errno::ENOENT
|
60
|
+
raise BadCommand, cmd.inspect
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
rescue LoadError
|
65
|
+
# On platforms where open4 is unavailable, we fallback to running using
|
66
|
+
# the backtick method of Kernel.
|
67
|
+
def run_real(cmd)
|
68
|
+
out = `#{cmd}`
|
69
|
+
raise BadCommand, cmd.inspect if $?.exitstatus == 127
|
70
|
+
raise Failed, "#{cmd.inspect} exited with status: #{$?.exitstatus}" unless $?.success? || $?.exitstatus == 1
|
71
|
+
out
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "piston/git/client"
|
2
|
+
require "piston/revision"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Piston
|
6
|
+
module Git
|
7
|
+
class Commit < Piston::Revision
|
8
|
+
class InvalidCommit < RuntimeError; end
|
9
|
+
class Gone < InvalidCommit; end
|
10
|
+
|
11
|
+
alias_method :commit, :revision
|
12
|
+
attr_reader :sha1
|
13
|
+
|
14
|
+
def initialize(repository, revision, recalled_values={})
|
15
|
+
super
|
16
|
+
@revision = 'master' if @revision.upcase == 'HEAD'
|
17
|
+
end
|
18
|
+
|
19
|
+
def client
|
20
|
+
@client ||= Piston::Git::Client.instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def git(*args)
|
24
|
+
client.git(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def recalled_commit_id
|
28
|
+
recalled_values[Piston::Git::COMMIT]
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate!
|
32
|
+
begin
|
33
|
+
data = git("ls-remote", @repository.url)
|
34
|
+
self
|
35
|
+
rescue Piston::Git::Client::CommandError
|
36
|
+
raise Piston::Git::Commit::Gone, "Repository at #{@repository.url} does not exist anymore"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def name
|
41
|
+
commit[0,7]
|
42
|
+
end
|
43
|
+
|
44
|
+
def branch_name
|
45
|
+
"my-#{commit}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def checkout_to(dir)
|
49
|
+
super
|
50
|
+
git(:clone, repository.url, @dir)
|
51
|
+
Dir.chdir(@dir) do
|
52
|
+
git(:checkout, "-b", branch_name, commit)
|
53
|
+
response = git(:log, "-n", "1")
|
54
|
+
@sha1 = $1 if response =~ /commit\s+([a-f\d]{40})/i
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_to(commit)
|
59
|
+
raise ArgumentError, "Commit #{self.commit} of #{repository.url} was never checked out -- can't update" unless @dir
|
60
|
+
|
61
|
+
Dir.chdir(@dir) do
|
62
|
+
logger.debug {"Saving old changes before updating"}
|
63
|
+
git(:commit, '-a', '-m', 'old changes')
|
64
|
+
logger.debug {"Merging old changes with #{commit}"}
|
65
|
+
git(:merge, '--squash', commit)
|
66
|
+
output = git(:status)
|
67
|
+
added = output.scan(/new file:\s+(.*)$/).flatten
|
68
|
+
deleted = output.scan(/deleted:\s+(.*)$/).flatten
|
69
|
+
renamed = output.scan(/renamed:\s+(.+) -> (.+)$/)
|
70
|
+
[added, deleted, renamed]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def remember_values
|
75
|
+
# find last commit for +commit+ if it wasn't checked out
|
76
|
+
@sha1 = git('ls-remote', repository.url, commit).match(/\w+/)[0] unless @sha1
|
77
|
+
# if ls-remote returns nothing, +commit+ must be a commit, not a branch
|
78
|
+
@sha1 = commit unless @sha1
|
79
|
+
{ Piston::Git::COMMIT => @sha1, Piston::Git::BRANCH => commit }
|
80
|
+
end
|
81
|
+
|
82
|
+
def each
|
83
|
+
raise ArgumentError, "Never cloned + checked out" if @dir.nil?
|
84
|
+
@dir.find do |path|
|
85
|
+
Find.prune if path.to_s =~ %r{/[.]git}
|
86
|
+
next if @dir == path
|
87
|
+
next if File.directory?(path)
|
88
|
+
yield path.relative_path_from(@dir)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def remotely_modified
|
93
|
+
branch = recalled_values[Piston::Git::BRANCH]
|
94
|
+
logger.debug {"Get last commit in #{branch} of #{repository.url}"}
|
95
|
+
commit = git('ls-remote', repository.url, branch).match(/\w+/)
|
96
|
+
# when we update to a commit, instead latest commit of a branch, +branch+ will be a commit, and ls-remote can return nil
|
97
|
+
commit = commit[0] unless commit.nil?
|
98
|
+
commit != self.commit
|
99
|
+
end
|
100
|
+
|
101
|
+
def exclude_for_diff
|
102
|
+
Piston::Git::EXCLUDE
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
"commit #{sha1}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def resolve!
|
110
|
+
# NOP, because @sha1 is what we want
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|