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.
Files changed (73) hide show
  1. data/History.txt +19 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +109 -0
  4. data/README.txt +136 -0
  5. data/VERSION.yml +4 -0
  6. data/bin/piston +5 -0
  7. data/lib/piston.rb +18 -0
  8. data/lib/piston/cli.rb +391 -0
  9. data/lib/piston/commands.rb +4 -0
  10. data/lib/piston/commands/base.rb +44 -0
  11. data/lib/piston/commands/convert.rb +26 -0
  12. data/lib/piston/commands/diff.rb +12 -0
  13. data/lib/piston/commands/import.rb +43 -0
  14. data/lib/piston/commands/info.rb +14 -0
  15. data/lib/piston/commands/lock_unlock.rb +21 -0
  16. data/lib/piston/commands/status.rb +40 -0
  17. data/lib/piston/commands/update.rb +34 -0
  18. data/lib/piston/commands/upgrade.rb +20 -0
  19. data/lib/piston/git.rb +13 -0
  20. data/lib/piston/git/client.rb +76 -0
  21. data/lib/piston/git/commit.rb +114 -0
  22. data/lib/piston/git/repository.rb +63 -0
  23. data/lib/piston/git/working_copy.rb +142 -0
  24. data/lib/piston/repository.rb +61 -0
  25. data/lib/piston/revision.rb +83 -0
  26. data/lib/piston/svn.rb +15 -0
  27. data/lib/piston/svn/client.rb +88 -0
  28. data/lib/piston/svn/repository.rb +67 -0
  29. data/lib/piston/svn/revision.rb +112 -0
  30. data/lib/piston/svn/working_copy.rb +182 -0
  31. data/lib/piston/version.rb +9 -0
  32. data/lib/piston/working_copy.rb +334 -0
  33. data/lib/subclass_responsibility_error.rb +2 -0
  34. data/test/integration_helpers.rb +35 -0
  35. data/test/spec_suite.rb +4 -0
  36. data/test/test_helper.rb +83 -0
  37. data/test/unit/git/commit/test_checkout.rb +31 -0
  38. data/test/unit/git/commit/test_each.rb +30 -0
  39. data/test/unit/git/commit/test_rememberance.rb +22 -0
  40. data/test/unit/git/commit/test_validation.rb +34 -0
  41. data/test/unit/git/repository/test_at.rb +23 -0
  42. data/test/unit/git/repository/test_basename.rb +12 -0
  43. data/test/unit/git/repository/test_branchanme.rb +15 -0
  44. data/test/unit/git/repository/test_guessing.rb +32 -0
  45. data/test/unit/git/working_copy/test_copying.rb +25 -0
  46. data/test/unit/git/working_copy/test_creation.rb +22 -0
  47. data/test/unit/git/working_copy/test_existence.rb +18 -0
  48. data/test/unit/git/working_copy/test_finalization.rb +15 -0
  49. data/test/unit/git/working_copy/test_guessing.rb +35 -0
  50. data/test/unit/git/working_copy/test_rememberance.rb +22 -0
  51. data/test/unit/svn/repository/test_at.rb +19 -0
  52. data/test/unit/svn/repository/test_basename.rb +24 -0
  53. data/test/unit/svn/repository/test_guessing.rb +45 -0
  54. data/test/unit/svn/revision/test_checkout.rb +28 -0
  55. data/test/unit/svn/revision/test_each.rb +22 -0
  56. data/test/unit/svn/revision/test_rememberance.rb +38 -0
  57. data/test/unit/svn/revision/test_validation.rb +50 -0
  58. data/test/unit/svn/working_copy/test_copying.rb +26 -0
  59. data/test/unit/svn/working_copy/test_creation.rb +16 -0
  60. data/test/unit/svn/working_copy/test_existence.rb +23 -0
  61. data/test/unit/svn/working_copy/test_externals.rb +56 -0
  62. data/test/unit/svn/working_copy/test_finalization.rb +17 -0
  63. data/test/unit/svn/working_copy/test_guessing.rb +18 -0
  64. data/test/unit/svn/working_copy/test_rememberance.rb +26 -0
  65. data/test/unit/test_info.rb +37 -0
  66. data/test/unit/test_lock_unlock.rb +47 -0
  67. data/test/unit/test_repository.rb +51 -0
  68. data/test/unit/test_revision.rb +31 -0
  69. data/test/unit/working_copy/test_guessing.rb +35 -0
  70. data/test/unit/working_copy/test_info.rb +14 -0
  71. data/test/unit/working_copy/test_rememberance.rb +42 -0
  72. data/test/unit/working_copy/test_validate.rb +63 -0
  73. metadata +178 -0
@@ -0,0 +1,4 @@
1
+ dir = File.dirname(__FILE__)
2
+ Dir["#{dir}/commands/**/*.rb"].each do |f|
3
+ require f.gsub("#{File.expand_path("#{File.dirname(f)}/../..")}/", "")
4
+ end
@@ -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,12 @@
1
+ require "piston/commands/base"
2
+
3
+ module Piston
4
+ module Commands
5
+ class Diff < Piston::Commands::Base
6
+ def run
7
+ working_copy = working_copy!(options[:wcdir])
8
+ working_copy.diff
9
+ end
10
+ end
11
+ end
12
+ 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,14 @@
1
+ require "piston/commands/base"
2
+
3
+ module Piston
4
+ module Commands
5
+ class Info < Piston::Commands::Base
6
+ attr_reader :options
7
+
8
+ def run(wcdir)
9
+ working_copy = working_copy!(wcdir)
10
+ working_copy.info.to_yaml
11
+ end
12
+ end
13
+ end
14
+ 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,13 @@
1
+ require "piston/git/client"
2
+ require "piston/git/repository"
3
+ require "piston/git/commit"
4
+ require "piston/git/working_copy"
5
+
6
+ module Piston
7
+ module Git
8
+ URL = "url"
9
+ COMMIT = "commit"
10
+ BRANCH = "branch"
11
+ EXCLUDE = [".git"]
12
+ end
13
+ end
@@ -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