piston 1.4.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +24 -0
- data/License.txt +20 -0
- data/Manifest.txt +109 -0
- data/{README → README.txt} +14 -10
- data/VERSION.yml +4 -0
- data/bin/piston +3 -8
- data/lib/piston/cli.rb +121 -0
- data/lib/piston/commands/base.rb +44 -0
- data/lib/piston/commands/convert.rb +23 -71
- data/lib/piston/commands/diff.rb +14 -46
- data/lib/piston/commands/import.rb +48 -57
- data/lib/piston/commands/info.rb +24 -0
- data/lib/piston/commands/lock_unlock.rb +26 -0
- data/lib/piston/commands/status.rb +29 -54
- data/lib/piston/commands/update.rb +35 -122
- data/lib/piston/commands/upgrade.rb +26 -0
- data/lib/piston/commands.rb +4 -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 +145 -0
- data/lib/piston/git.rb +13 -0
- data/lib/piston/repository.rb +61 -0
- data/lib/piston/revision.rb +83 -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 +184 -0
- data/lib/piston/svn.rb +15 -0
- data/lib/piston/version.rb +9 -7
- data/lib/piston/working_copy.rb +334 -0
- data/lib/piston.rb +13 -64
- data/lib/subclass_responsibility_error.rb +2 -0
- data/test/integration_helpers.rb +36 -0
- data/test/spec_suite.rb +4 -0
- data/test/test_helper.rb +92 -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 +132 -31
- data/CHANGELOG +0 -81
- data/LICENSE +0 -19
- data/Rakefile +0 -63
- data/contrib/piston +0 -43
- data/lib/core_ext/range.rb +0 -5
- data/lib/core_ext/string.rb +0 -9
- data/lib/piston/command.rb +0 -68
- data/lib/piston/command_error.rb +0 -6
- data/lib/piston/commands/lock.rb +0 -30
- data/lib/piston/commands/switch.rb +0 -139
- data/lib/piston/commands/unlock.rb +0 -29
- data/lib/transat/parser.rb +0 -189
@@ -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,145 @@
|
|
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 do |item|
|
68
|
+
item.mkdir unless item.exist?
|
69
|
+
git(:add, item)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete(deleted)
|
75
|
+
Dir.chdir(path) do
|
76
|
+
deleted.each { |item| git(:rm, item) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def rename(renamed)
|
81
|
+
Dir.chdir(path) do
|
82
|
+
renamed.each { |from, to| git(:mv, from, to) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def downgrade_to(revision)
|
87
|
+
logger.debug {"Creating a branch to copy changes from remote repository"}
|
88
|
+
Dir.chdir(path) { git(:checkout, '-b', "my-#{revision}", revision) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def merge_local_changes(revision)
|
92
|
+
from_revision = current_revision
|
93
|
+
Dir.chdir(path) do
|
94
|
+
begin
|
95
|
+
logger.debug {"Saving changes in temporary branch"}
|
96
|
+
git(:commit, '-a', '-m', 'merging')
|
97
|
+
logger.debug {"Return to previous branch"}
|
98
|
+
git(:checkout, revision)
|
99
|
+
logger.debug {"Merge changes from temporary branch"}
|
100
|
+
git(:merge, '--squash', from_revision)
|
101
|
+
rescue Piston::Git::Client::CommandError
|
102
|
+
git(:checkout, revision)
|
103
|
+
ensure
|
104
|
+
logger.debug {"Deleting temporary branch"}
|
105
|
+
git(:branch, '-D', from_revision)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def locally_modified
|
111
|
+
logger.debug {"Get last changed revision for #{yaml_path}"}
|
112
|
+
# get latest commit for .piston.yml
|
113
|
+
initial_revision = last_changed_revision(yaml_path)
|
114
|
+
logger.debug {"Get last log line for #{path} after #{initial_revision}"}
|
115
|
+
# get latest revisions for this working copy since last update
|
116
|
+
Dir.chdir(path) { not git(:log, '-n', '1', "#{initial_revision}..", '.').empty? }
|
117
|
+
end
|
118
|
+
|
119
|
+
def exclude_for_diff
|
120
|
+
Piston::Git::EXCLUDE
|
121
|
+
end
|
122
|
+
|
123
|
+
def status(subpath=nil)
|
124
|
+
Dir.chdir(path) do
|
125
|
+
git(:status).split("\n").inject([]) do |memo, line|
|
126
|
+
next memo unless line =~ /\s(\w+):\s+(.*)$/
|
127
|
+
memo << [$1, $2]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
def current_revision
|
134
|
+
Dir.chdir(path) { git(:branch).match(/^\*\s+(.+)$/)[1] }
|
135
|
+
end
|
136
|
+
|
137
|
+
def last_changed_revision(path)
|
138
|
+
path = Pathname.new(path) unless path.is_a? Pathname
|
139
|
+
path = path.relative_path_from(self.path) unless path.relative?
|
140
|
+
logger.debug {"Get last log line for #{path}"}
|
141
|
+
Dir.chdir(self.path) { git(:log, '-n', '1', path).match(/commit\s+(.*)$/)[1] }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/piston/git.rb
ADDED
@@ -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
|
@@ -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
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Svn
|
5
|
+
class Repository < Piston::Repository
|
6
|
+
# Register ourselves as a repository handler
|
7
|
+
Piston::Repository.add_handler self
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def understands_url?(url)
|
11
|
+
uri = URI.parse(url)
|
12
|
+
case uri.scheme
|
13
|
+
when "svn", /^svn\+/
|
14
|
+
true
|
15
|
+
when "http", "https", "file"
|
16
|
+
# Have to contact server to know
|
17
|
+
result = svn(:info, url) rescue :failed
|
18
|
+
result == :failed ? false : true
|
19
|
+
else
|
20
|
+
# Don't know how to handle this scheme.
|
21
|
+
# Let someone else handle it
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def client
|
26
|
+
@@client ||= Piston::Svn::Client.instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def svn(*args)
|
30
|
+
client.svn(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def repository_type
|
34
|
+
'svn'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def svn(*args)
|
39
|
+
self.class.svn(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def at(revision)
|
43
|
+
if revision.respond_to?(:keys) then
|
44
|
+
rev = revision[Piston::Svn::REMOTE_REV]
|
45
|
+
Piston::Svn::Revision.new(self, rev, revision)
|
46
|
+
else
|
47
|
+
case
|
48
|
+
when revision == :head
|
49
|
+
Piston::Svn::Revision.new(self, "HEAD")
|
50
|
+
when revision.to_i != 0
|
51
|
+
Piston::Svn::Revision.new(self, revision.to_i)
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Invalid revision argument: #{revision.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def basename
|
59
|
+
if self.url =~ /trunk|branches|tags/ then
|
60
|
+
self.url.sub(%r{/(?:trunk|branches|tags).*$}, "").split("/").last
|
61
|
+
else
|
62
|
+
self.url.split("/").last
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "piston/revision"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module Piston
|
5
|
+
module Svn
|
6
|
+
class Revision < Piston::Revision
|
7
|
+
class InvalidRevision < RuntimeError; end
|
8
|
+
class RepositoryMoved < InvalidRevision; end
|
9
|
+
class UuidChanged < InvalidRevision; end
|
10
|
+
|
11
|
+
def client
|
12
|
+
@client ||= Piston::Svn::Client.instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def svn(*args)
|
16
|
+
client.svn(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
"r#{revision}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def checkout_to(dir)
|
24
|
+
super
|
25
|
+
answer = svn(:checkout, "--revision", revision, repository.url, dir)
|
26
|
+
if answer =~ /Checked out revision (\d+)[.]/ then
|
27
|
+
if revision == "HEAD" then
|
28
|
+
@revision = $1.to_i
|
29
|
+
elsif revision != $1.to_i then
|
30
|
+
raise InvalidRevision, "Did not get the revision I wanted to checkout. Subversion checked out #{$1}, I wanted #{revision}"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise InvalidRevision, "Could not checkout revision #{revision} from #{repository.url} to #{dir}\n#{answer}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_to(revision)
|
38
|
+
raise ArgumentError, "Revision #{self.revision} of #{repository.url} was never checked out -- can't update" unless @dir
|
39
|
+
|
40
|
+
answer = svn(:update, "--non-interactive", "--revision", revision, @dir)
|
41
|
+
if answer =~ /(Updated to|At) revision (\d+)[.]/ then
|
42
|
+
if revision == "HEAD" then
|
43
|
+
@revision = $2.to_i
|
44
|
+
elsif revision != $2.to_i then
|
45
|
+
raise InvalidRevision, "Did not get the revision I wanted to update. Subversion update to #{$1}, I wanted #{revision}"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
raise InvalidRevision, "Could not update #{@dir} to revision #{revision} from #{repository.url}\n#{answer}"
|
49
|
+
end
|
50
|
+
added = relative_paths(answer.scan(/^A\s+(.*)$/).flatten)
|
51
|
+
deleted = relative_paths(answer.scan(/^D\s+(.*)$/).flatten)
|
52
|
+
renamed = []
|
53
|
+
[added, deleted, renamed]
|
54
|
+
end
|
55
|
+
|
56
|
+
def remember_values
|
57
|
+
str = svn(:info, "--revision", revision, repository.url)
|
58
|
+
raise Failed, "Could not get 'svn info' from #{repository.url} at revision #{revision}" if str.nil? || str.chomp.strip.empty?
|
59
|
+
info = YAML.load(str)
|
60
|
+
{ Piston::Svn::UUID => info["Repository UUID"],
|
61
|
+
Piston::Svn::REMOTE_REV => info["Revision"]}
|
62
|
+
end
|
63
|
+
|
64
|
+
def each
|
65
|
+
raise ArgumentError, "Revision #{revision} of #{repository.url} was never checked out -- can't iterate over files" unless @dir
|
66
|
+
|
67
|
+
svn(:ls, "--recursive", @dir).split("\n").each do |relpath|
|
68
|
+
next if relpath =~ %r{/$}
|
69
|
+
yield relpath.chomp
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate!
|
74
|
+
data = svn(:info, "--revision", revision, repository.url)
|
75
|
+
info = YAML.load(data)
|
76
|
+
actual_uuid = info["Repository UUID"]
|
77
|
+
raise RepositoryMoved, "Repository at #{repository.url} does not exist anymore:\n#{data}" if actual_uuid.blank?
|
78
|
+
raise UuidChanged, "Expected repository at #{repository.url} to have UUID #{recalled_uuid} but found #{actual_uuid}" if recalled_uuid != actual_uuid
|
79
|
+
end
|
80
|
+
|
81
|
+
def recalled_uuid
|
82
|
+
recalled_values[Piston::Svn::UUID]
|
83
|
+
end
|
84
|
+
|
85
|
+
def remotely_modified
|
86
|
+
logger.debug {"Get last revision in #{repository.url}"}
|
87
|
+
data = svn(:info, repository.url)
|
88
|
+
info = YAML.load(data)
|
89
|
+
latest_revision = info["Last Changed Rev"].to_i
|
90
|
+
revision < latest_revision
|
91
|
+
end
|
92
|
+
|
93
|
+
def exclude_for_diff
|
94
|
+
Piston::Svn::EXCLUDE
|
95
|
+
end
|
96
|
+
|
97
|
+
def resolve!
|
98
|
+
logger.debug {"Resolving #{@revision} to it's real value"}
|
99
|
+
return if @revision.to_i == @revision && !@revision.blank?
|
100
|
+
data = YAML.load(svn(:info, repository.url))
|
101
|
+
@revision = data["Last Changed Rev"].to_i
|
102
|
+
logger.debug {"Resolved #{@revision}"}
|
103
|
+
@revision
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def relative_paths(paths)
|
108
|
+
paths.map { |item| Pathname.new(item).relative_path_from(@dir) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|