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,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
|