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,63 @@
1
+ require "piston/git/commit"
2
+ require "uri"
3
+
4
+ module Piston
5
+ module Git
6
+ class Repository < Piston::Repository
7
+ Piston::Repository.add_handler self
8
+
9
+ class << self
10
+ def understands_url?(url)
11
+ uri = URI.parse(url) rescue nil
12
+ return true if uri && %w(git).include?(uri.scheme)
13
+
14
+ begin
15
+ response = git("ls-remote", "--heads", url.sub(/\?.+$/, ""))
16
+ return false if response.nil? || response.strip.chomp.empty?
17
+ !!(response =~ /[a-f\d]{40}\s/)
18
+ rescue Piston::Git::Client::CommandError
19
+ false
20
+ end
21
+ end
22
+
23
+ def client
24
+ @@client ||= Piston::Git::Client.instance
25
+ end
26
+
27
+ def git(*args)
28
+ client.git(*args)
29
+ end
30
+
31
+ def repository_type
32
+ 'git'
33
+ end
34
+ end
35
+
36
+ attr_reader :branchname
37
+
38
+ def initialize(url)
39
+ @branchname = url.split("?")[1]
40
+ super(url.sub(/\?.+$/, ""))
41
+ end
42
+
43
+ def git(*args)
44
+ self.class.git(*args)
45
+ end
46
+
47
+ def at(commit)
48
+ case commit
49
+ when Hash
50
+ Piston::Git::Commit.new(self, commit[Piston::Git::COMMIT], commit)
51
+ when :head
52
+ Piston::Git::Commit.new(self, "HEAD")
53
+ else
54
+ Piston::Git::Commit.new(self, commit)
55
+ end
56
+ end
57
+
58
+ def basename
59
+ self.url.split("/").last.sub(".git", "")
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,142 @@
1
+ require "piston/working_copy"
2
+ require "piston/git/client"
3
+
4
+ module Piston
5
+ module Git
6
+ class WorkingCopy < Piston::WorkingCopy
7
+ # Register ourselves as a handler for working copies
8
+ Piston::WorkingCopy.add_handler self
9
+
10
+ class << self
11
+ def understands_dir?(dir)
12
+ path = dir
13
+ begin
14
+ begin
15
+ logger.debug {"git status on #{path}"}
16
+ Dir.chdir(path) do
17
+ response = git(:status)
18
+ return true if response =~ /# On branch /
19
+ end
20
+ rescue Errno::ENOENT
21
+ # NOP, we assume this is simply because the folder hasn't been created yet
22
+ path = path.parent
23
+ retry unless path.to_s == "/"
24
+ return false
25
+ end
26
+ rescue Piston::Git::Client::BadCommand
27
+ # NOP, as we return false below
28
+ rescue Piston::Git::Client::CommandError
29
+ # This is certainly not a Git repository
30
+ false
31
+ end
32
+
33
+ false
34
+ end
35
+
36
+ def client
37
+ @@client ||= Piston::Git::Client.instance
38
+ end
39
+
40
+ def git(*args)
41
+ client.git(*args)
42
+ end
43
+ end
44
+
45
+ def git(*args)
46
+ self.class.git(*args)
47
+ end
48
+
49
+ def create
50
+ path.mkpath rescue nil
51
+ end
52
+
53
+ def exist?
54
+ path.directory?
55
+ end
56
+
57
+ def after_remember(path)
58
+ Dir.chdir(self.path) { git(:add, path.relative_path_from(self.path)) }
59
+ end
60
+
61
+ def finalize
62
+ Dir.chdir(path) { git(:add, ".") }
63
+ end
64
+
65
+ def add(added)
66
+ Dir.chdir(path) do
67
+ added.each { |item| git(:add, item) }
68
+ end
69
+ end
70
+
71
+ def delete(deleted)
72
+ Dir.chdir(path) do
73
+ deleted.each { |item| git(:rm, item) }
74
+ end
75
+ end
76
+
77
+ def rename(renamed)
78
+ Dir.chdir(path) do
79
+ renamed.each { |from, to| git(:mv, from, to) }
80
+ end
81
+ end
82
+
83
+ def downgrade_to(revision)
84
+ logger.debug {"Creating a branch to copy changes from remote repository"}
85
+ Dir.chdir(path) { git(:checkout, '-b', "my-#{revision}", revision) }
86
+ end
87
+
88
+ def merge_local_changes(revision)
89
+ from_revision = current_revision
90
+ Dir.chdir(path) do
91
+ begin
92
+ logger.debug {"Saving changes in temporary branch"}
93
+ git(:commit, '-a', '-m', 'merging')
94
+ logger.debug {"Return to previous branch"}
95
+ git(:checkout, revision)
96
+ logger.debug {"Merge changes from temporary branch"}
97
+ git(:merge, '--squash', from_revision)
98
+ rescue Piston::Git::Client::CommandError
99
+ git(:checkout, revision)
100
+ ensure
101
+ logger.debug {"Deleting temporary branch"}
102
+ git(:branch, '-D', from_revision)
103
+ end
104
+ end
105
+ end
106
+
107
+ def locally_modified
108
+ logger.debug {"Get last changed revision for #{yaml_path}"}
109
+ # get latest commit for .piston.yml
110
+ initial_revision = last_changed_revision(yaml_path)
111
+ logger.debug {"Get last log line for #{path} after #{initial_revision}"}
112
+ # get latest revisions for this working copy since last update
113
+ Dir.chdir(path) { not git(:log, '-n', '1', "#{initial_revision}..", '.').empty? }
114
+ end
115
+
116
+ def exclude_for_diff
117
+ Piston::Git::EXCLUDE
118
+ end
119
+
120
+ def status(subpath=nil)
121
+ Dir.chdir(path) do
122
+ git(:status).split("\n").inject([]) do |memo, line|
123
+ next memo unless line =~ /\s(\w+):\s+(.*)$/
124
+ memo << [$1, $2]
125
+ end
126
+ end
127
+ end
128
+
129
+ protected
130
+ def current_revision
131
+ Dir.chdir(path) { git(:branch).match(/^\*\s+(.+)$/)[1] }
132
+ end
133
+
134
+ def last_changed_revision(path)
135
+ path = Pathname.new(path) unless path.is_a? Pathname
136
+ path = path.relative_path_from(self.path) unless path.relative?
137
+ logger.debug {"Get last log line for #{path}"}
138
+ Dir.chdir(self.path) { git(:log, '-n', '1', path).match(/commit\s+(.*)$/)[1] }
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,61 @@
1
+ require "piston/revision"
2
+
3
+ module Piston
4
+ class Repository
5
+ class UnhandledUrl < RuntimeError; end
6
+
7
+ class << self
8
+ def logger
9
+ @@logger ||= Log4r::Logger["handler"]
10
+ end
11
+
12
+ def guess(url)
13
+ logger.info {"Guessing the repository type of #{url.inspect}"}
14
+
15
+ handler = handlers.detect do |handler|
16
+ logger.debug {"Asking #{handler}"}
17
+ handler.understands_url?(url)
18
+ end
19
+
20
+ raise UnhandledUrl unless handler
21
+
22
+ handler.new(url)
23
+ end
24
+
25
+ @@handlers = Array.new
26
+ def add_handler(handler)
27
+ @@handlers << handler
28
+ end
29
+
30
+ def handlers
31
+ @@handlers
32
+ end
33
+ end
34
+
35
+ attr_reader :url
36
+
37
+ def initialize(url)
38
+ @url = url
39
+ end
40
+
41
+ def logger
42
+ self.class.logger
43
+ end
44
+
45
+ def at(revision)
46
+ raise SubclassResponsibilityError, "Piston::Repository#at should be implemented by a subclass."
47
+ end
48
+
49
+ def to_s
50
+ @url
51
+ end
52
+
53
+ def inspect
54
+ "Piston::Repository(#{@url})"
55
+ end
56
+
57
+ def ==(other)
58
+ url == other.url
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,83 @@
1
+ module Piston
2
+ class Revision
3
+ include Enumerable
4
+
5
+ class << self
6
+ def logger
7
+ @@logger ||= Log4r::Logger["handler"]
8
+ end
9
+ end
10
+
11
+ attr_reader :repository, :revision, :recalled_values, :dir
12
+
13
+ def initialize(repository, revision, recalled_values={})
14
+ @repository, @revision, @recalled_values = repository, revision, recalled_values
15
+ end
16
+
17
+ def url
18
+ repository.url
19
+ end
20
+
21
+ def name
22
+ @revision
23
+ end
24
+
25
+ def logger
26
+ self.class.logger
27
+ end
28
+
29
+ def to_s
30
+ "revision #{@revision}"
31
+ end
32
+
33
+ def inspect
34
+ "Piston::Revision(#{@repository.url}@#{@revision})"
35
+ end
36
+
37
+ # Retrieve a copy of this repository into +dir+.
38
+ def checkout_to(dir)
39
+ logger.debug {"Checking out #{@repository}@#{@revision} into #{dir}"}
40
+ @dir = dir.kind_of?(Pathname) ? dir : Pathname.new(dir)
41
+ end
42
+
43
+ # Update a copy of this repository to revision +to+.
44
+ def update_to(to, lock)
45
+ raise SubclassResponsibilityError, "Piston::Revision#update_to should be implemented by a subclass."
46
+ end
47
+
48
+ # What values does this revision want to remember for the future ?
49
+ def remember_values
50
+ logger.debug {"Generating remember values"}
51
+ {}
52
+ end
53
+
54
+ # Yields each file of this revision in turn to our caller.
55
+ def each
56
+ end
57
+
58
+ # Copies +relpath+ (relative to ourselves) to +abspath+ (an absolute path).
59
+ def copy_to(relpath, abspath)
60
+ raise ArgumentError, "Revision #{revision} of #{repository.url} was never checked out -- can't iterate over files" unless @dir
61
+
62
+ Pathname.new(abspath).dirname.mkpath
63
+ FileUtils.cp(@dir + relpath, abspath)
64
+ end
65
+
66
+ # Copies +abspath+ (an absolute path) to +relpath+ (relative to ourselves).
67
+ def copy_from(abspath, relpath)
68
+ raise ArgumentError, "Revision #{revision} of #{repository.url} was never checked out -- can't iterate over files" unless @dir
69
+
70
+ target = @dir + relpath
71
+ Pathname.new(target).dirname.mkpath
72
+ FileUtils.cp(abspath, target)
73
+ end
74
+
75
+ def remotely_modified
76
+ raise SubclassResponsibilityError, "Piston::Revision#remotely_modified should be implemented by a subclass."
77
+ end
78
+
79
+ def exclude_for_diff
80
+ raise SubclassResponsibilityError, "Piston::Revision#exclude_for_diff should be implemented by a subclass."
81
+ end
82
+ end
83
+ end
data/lib/piston/svn.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "piston/svn/client"
2
+ require "piston/svn/repository"
3
+ require "piston/svn/revision"
4
+ require "piston/svn/working_copy"
5
+
6
+ module Piston
7
+ module Svn
8
+ ROOT = "piston:root"
9
+ UUID = "piston:uuid"
10
+ REMOTE_REV = "piston:remote-revision"
11
+ LOCAL_REV = "piston:local-revision"
12
+ LOCKED = "piston:locked"
13
+ EXCLUDE = [".svn"]
14
+ end
15
+ end
@@ -0,0 +1,88 @@
1
+ require "singleton"
2
+
3
+ module Piston
4
+ module Svn
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 svnadmin(*args)
21
+ run_cmd :svnadmin, *args
22
+ end
23
+
24
+ def svn(*args)
25
+ run_cmd :svn, *args
26
+ end
27
+
28
+ def svnlook(*args)
29
+ run_cmd :svnlook, *args
30
+ end
31
+
32
+ def svnversion(*args)
33
+ run_cmd :svnversion, *args
34
+ end
35
+
36
+ private
37
+ def run_cmd(executable, *args)
38
+ args.collect! {|arg| arg.to_s =~ /\s|\*|\?|"|\n|\r/ ? %Q('#{arg}') : arg}
39
+ args.collect! {|arg| arg ? arg : '""'}
40
+ cmd = %Q|#{executable} #{args.join(' ')}|
41
+ logger.debug {"> " + cmd}
42
+
43
+ original_language = ENV["LANGUAGE"]
44
+ begin
45
+ ENV["LANGUAGE"] = "C"
46
+ value = run_real(cmd)
47
+ out_logger.info {"< " + value} unless (value || "").strip.empty?
48
+ return value
49
+ ensure
50
+ ENV["LANGUAGE"] = original_language
51
+ end
52
+ end
53
+
54
+ begin
55
+ raise LoadError, "Not implemented on Win32 machines" if RUBY_PLATFORM =~ /mswin32/
56
+
57
+ begin
58
+ require "rubygems"
59
+ rescue LoadError
60
+ # NOP -- attempt to load without Rubygems
61
+ end
62
+
63
+ require "open4"
64
+
65
+ def run_real(cmd)
66
+ begin
67
+ pid, stdin, stdout, stderr = Open4::popen4(cmd)
68
+ _, cmdstatus = Process.waitpid2(pid)
69
+ raise CommandError, "#{cmd.inspect} exited with status: #{cmdstatus.exitstatus}\n#{stderr.read}" unless cmdstatus.success?
70
+ return stdout.read
71
+ rescue Errno::ENOENT
72
+ raise BadCommand, cmd.inspect
73
+ end
74
+ end
75
+
76
+ rescue LoadError
77
+ # On platforms where open4 is unavailable, we fallback to running using
78
+ # the backtick method of Kernel.
79
+ def run_real(cmd)
80
+ out = `#{cmd}`
81
+ raise BadCommand, cmd.inspect if $?.exitstatus == 127
82
+ raise Failed, "#{cmd.inspect} exited with status: #{$?.exitstatus}" unless $?.success?
83
+ out
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end