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
@@ -1,74 +1,65 @@
|
|
1
|
-
require "piston"
|
2
|
-
require "piston/command"
|
1
|
+
require "piston/commands/base"
|
3
2
|
|
4
3
|
module Piston
|
5
4
|
module Commands
|
6
|
-
class Import < Piston::
|
7
|
-
|
8
|
-
raise Piston::CommandError, "Missing REPOS_URL argument" if args.empty?
|
5
|
+
class Import < Piston::Commands::Base
|
6
|
+
attr_reader :options
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
if File.exists?(dir) then
|
15
|
-
raise Piston::CommandError, "Target folder already exists" unless force
|
16
|
-
svn :revert, '--recursive', dir
|
17
|
-
FileUtils.rm_rf(dir)
|
18
|
-
end
|
19
|
-
|
20
|
-
my_info = YAML::load(svn(:info, File.join(dir, '..')))
|
21
|
-
my_revision = YAML::load(svn(:info, my_info['URL']))['Revision']
|
22
|
-
raise Piston::CommandError, "#{File.expand_path(File.join(dir, '..'))} is out of date - run svn update" unless my_info['Revision'] == my_revision
|
8
|
+
def repository_type
|
9
|
+
options[:repository_type]
|
10
|
+
end
|
23
11
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
next unless line =~ /Exported revision (\d+)./i
|
34
|
-
@revision = $1
|
35
|
-
break
|
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)
|
36
21
|
end
|
22
|
+
end
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
# Set the properties
|
42
|
-
svn :propset, Piston::ROOT, repos, dir
|
43
|
-
svn :propset, Piston::UUID, info['Repository UUID'], dir
|
44
|
-
svn :propset, Piston::REMOTE_REV, his_revision, dir
|
45
|
-
svn :propset, Piston::LOCAL_REV, my_revision, dir
|
46
|
-
svn :propset, Piston::LOCKED, revision, dir if lock
|
47
|
-
|
48
|
-
# Finish adding. If we get an error, at least the properties will be
|
49
|
-
# set and the user can handle the rest
|
50
|
-
svn :add, '--force', '--quiet', dir
|
24
|
+
def run(repository_url, target_revision, wcdir)
|
25
|
+
repository = select_repository(repository_url)
|
26
|
+
revision = repository.at(target_revision)
|
51
27
|
|
52
|
-
|
53
|
-
|
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)
|
54
32
|
|
55
|
-
|
56
|
-
|
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}"}
|
57
40
|
end
|
58
41
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
42
|
+
def start(*args)
|
43
|
+
repository_url = args.shift
|
44
|
+
wcdir = args.shift
|
62
45
|
|
63
|
-
|
64
|
-
DIR, defaulting to the last component of REPOS_URL if DIR is not present.
|
46
|
+
raise ArgumentError, "Required REPOSITORY argument missing" if repository_url.blank?
|
65
47
|
|
66
|
-
|
67
|
-
|
68
|
-
|
48
|
+
begin
|
49
|
+
self.run(repository_url, options[:revision] || options[:commit] || :head, wcdir)
|
50
|
+
rescue Piston::Repository::UnhandledUrl => e
|
51
|
+
supported_types = Piston::Repository.handlers.collect do |handler|
|
52
|
+
handler.repository_type
|
53
|
+
end
|
54
|
+
puts "Unsure how to handle:"
|
55
|
+
puts "\t#{repository_url.inspect}."
|
56
|
+
puts "You should try using --repository-type. Supported types are:"
|
57
|
+
supported_types.each do |type|
|
58
|
+
puts "\t#{type}"
|
59
|
+
end
|
69
60
|
|
70
|
-
|
71
|
-
|
61
|
+
exit 1
|
62
|
+
end
|
72
63
|
end
|
73
64
|
end
|
74
65
|
end
|
@@ -0,0 +1,24 @@
|
|
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
|
+
|
13
|
+
def start(*args)
|
14
|
+
args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir|
|
15
|
+
begin
|
16
|
+
run(wcdir)
|
17
|
+
rescue Piston::WorkingCopy::NotWorkingCopy
|
18
|
+
puts "#{wcdir} is not a working copy"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
|
20
|
+
def start(*args)
|
21
|
+
options[:wcdir] = args.first
|
22
|
+
run(true)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,75 +1,50 @@
|
|
1
|
-
require "piston"
|
2
|
-
require "piston/command"
|
3
|
-
require 'pp'
|
1
|
+
require "piston/commands/base"
|
4
2
|
|
5
3
|
module Piston
|
6
4
|
module Commands
|
7
|
-
class Status < Piston::
|
8
|
-
def run
|
9
|
-
#
|
10
|
-
|
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
11
|
repos = Hash.new
|
12
|
-
|
13
|
-
|
14
|
-
next unless line =~ /(\w.*) - /
|
15
|
-
repos[$1] = Hash.new
|
12
|
+
Pathname.glob(wcdir + '**/.piston.yml') do |path|
|
13
|
+
repos[path.dirname] = Hash.new
|
16
14
|
end
|
17
15
|
|
18
16
|
# Then, get their properties
|
19
|
-
repo = nil
|
20
|
-
svn(:proplist, '--verbose', *repos.keys).each_line do |line|
|
21
|
-
case line
|
22
|
-
when /'([^']+)'/
|
23
|
-
repo = repos[$1]
|
24
|
-
when /(piston:[-\w]+)\s*:\s*(.*)$/
|
25
|
-
repo[$1] = $2
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Determine their local status
|
30
17
|
repos.each_pair do |path, props|
|
31
|
-
|
32
|
-
|
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
|
33
24
|
end
|
34
25
|
|
35
|
-
# And their remote status, if required
|
36
|
-
repos.each_pair do |path, props|
|
37
|
-
log = svn(:log, '--revision', "#{props[Piston::REMOTE_REV]}:HEAD", '--quiet', '--limit', '2', props[Piston::ROOT])
|
38
|
-
props[:remotely_modified] = 'M' if log.count("\n") > 3
|
39
|
-
end if show_updates
|
40
|
-
|
41
26
|
# Display the results
|
42
27
|
repos.each_pair do |path, props|
|
43
|
-
|
44
|
-
props[:remotely_modified], props[
|
28
|
+
printf "%1s%1s %6s %s (%s)\n", props[:locally_modified],
|
29
|
+
props[:remotely_modified], props["lock"] ? 'locked' : '', path, props["repository_url"]
|
45
30
|
end
|
46
31
|
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.help
|
51
|
-
"Determines the current status of each pistoned directory"
|
32
|
+
puts "No pistonized folders found in #{wcdir}" if repos.empty?
|
52
33
|
end
|
53
34
|
|
54
|
-
def
|
55
|
-
|
56
|
-
usage: status [DIR [DIR...]]
|
57
|
-
|
58
|
-
Shows the status of one, many or all pistoned folders. The status is
|
59
|
-
returned in columns.
|
60
|
-
|
61
|
-
The first column's values are:
|
62
|
-
: Locally unchanged (space)
|
63
|
-
M: Locally modified since importing
|
64
|
-
|
65
|
-
The second column's values are blanks, unless the --show-updates is passed.
|
66
|
-
M: Remotely modified since importing
|
67
|
-
EOF
|
35
|
+
def show_updates
|
36
|
+
options[:show_updates]
|
68
37
|
end
|
69
38
|
|
70
|
-
|
71
|
-
|
72
|
-
|
39
|
+
def start(*args)
|
40
|
+
args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir|
|
41
|
+
begin
|
42
|
+
run(wcdir)
|
43
|
+
rescue Piston::WorkingCopy::NotWorkingCopy
|
44
|
+
puts "#{wcdir} is not a working copy"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
73
48
|
end
|
74
49
|
end
|
75
50
|
end
|
@@ -1,131 +1,44 @@
|
|
1
|
-
require "piston"
|
2
|
-
require "piston/command"
|
3
|
-
require 'find'
|
1
|
+
require "piston/commands/base"
|
4
2
|
|
5
3
|
module Piston
|
6
4
|
module Commands
|
7
|
-
class Update < Piston::
|
8
|
-
|
9
|
-
|
10
|
-
|
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}"}
|
11
30
|
end
|
12
31
|
end
|
13
32
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
else
|
24
|
-
new_status << line unless line =~ /^\?/
|
25
|
-
end
|
26
|
-
end
|
27
|
-
raise "Unable to parse status\n#{status}" unless new_local_rev
|
28
|
-
return skip(dir, "pending updates -- run \"svn update #{dir}\"\n#{new_status}") if new_status.size > 0
|
29
|
-
|
30
|
-
logging_stream.puts "Processing '#{dir}'..."
|
31
|
-
repos = svn(:propget, Piston::ROOT, dir).chomp
|
32
|
-
uuid = svn(:propget, Piston::UUID, dir).chomp
|
33
|
-
remote_revision = svn(:propget, Piston::REMOTE_REV, dir).chomp.to_i
|
34
|
-
local_revision = svn(:propget, Piston::LOCAL_REV, dir).chomp.to_i
|
35
|
-
local_revision = local_revision.succ
|
36
|
-
|
37
|
-
logging_stream.puts " Fetching remote repository's latest revision and UUID"
|
38
|
-
info = YAML::load(svn(:info, repos))
|
39
|
-
return skip(dir, "Repository UUID changed\n Expected #{uuid}\n Found #{info['Repository UUID']}\n Repository: #{repos}") unless uuid == info['Repository UUID']
|
40
|
-
|
41
|
-
new_remote_rev = info['Last Changed Rev'].to_i
|
42
|
-
return skip(dir, "unchanged from revision #{remote_revision}", false) if remote_revision == new_remote_rev
|
43
|
-
|
44
|
-
revisions = (remote_revision .. (revision || new_remote_rev))
|
45
|
-
|
46
|
-
logging_stream.puts " Restoring remote repository to known state at r#{revisions.first}"
|
47
|
-
svn :checkout, '--ignore-externals', '--quiet', '--revision', revisions.first, repos, dir.tmp
|
48
|
-
|
49
|
-
logging_stream.puts " Updating remote repository to r#{revisions.last}"
|
50
|
-
updates = svn :update, '--ignore-externals', '--revision', revisions.last, dir.tmp
|
51
|
-
|
52
|
-
logging_stream.puts " Processing adds/deletes"
|
53
|
-
merges = Array.new
|
54
|
-
changes = 0
|
55
|
-
updates.each_line do |line|
|
56
|
-
next unless line =~ %r{^([A-Z]).*\s+#{Regexp.escape(dir.tmp)}[\\/](.+)$}
|
57
|
-
op, file = $1, $2
|
58
|
-
changes += 1
|
59
|
-
|
60
|
-
case op
|
61
|
-
when 'A'
|
62
|
-
if File.directory?(File.join(dir.tmp, file)) then
|
63
|
-
svn :mkdir, '--quiet', File.join(dir, file)
|
64
|
-
else
|
65
|
-
copy(dir, file)
|
66
|
-
svn :add, '--quiet', '--force', File.join(dir, file)
|
67
|
-
end
|
68
|
-
when 'D'
|
69
|
-
svn :remove, '--quiet', '--force', File.join(dir, file)
|
70
|
-
else
|
71
|
-
copy(dir, file)
|
72
|
-
merges << file
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Determine if there are any local changes in the pistoned directory
|
77
|
-
log = svn(:log, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn, '--limit', '2', dir)
|
78
|
-
|
79
|
-
# If none, we skip the merge process
|
80
|
-
if local_revision < new_local_rev && log.count("\n") > 3 then
|
81
|
-
logging_stream.puts " Merging local changes back in"
|
82
|
-
merges.each do |file|
|
83
|
-
begin
|
84
|
-
svn(:merge, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn,
|
85
|
-
File.join(dir, file), File.join(dir, file))
|
86
|
-
rescue RuntimeError
|
87
|
-
next if $!.message =~ /Unable to find repository location for/
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
logging_stream.puts " Removing temporary files / folders"
|
93
|
-
FileUtils.rm_rf dir.tmp
|
94
|
-
|
95
|
-
logging_stream.puts " Updating Piston properties"
|
96
|
-
svn :propset, Piston::REMOTE_REV, revisions.last, dir
|
97
|
-
svn :propset, Piston::LOCAL_REV, new_local_rev, dir
|
98
|
-
svn :propset, Piston::LOCKED, revisions.last, dir if lock
|
99
|
-
|
100
|
-
logging_stream.puts " Updated to r#{revisions.last} (#{changes} changes)"
|
101
|
-
end
|
102
|
-
|
103
|
-
def copy(dir, file)
|
104
|
-
FileUtils.cp(File.join(dir.tmp, file), File.join(dir, file))
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.help
|
108
|
-
"Updates all or specified folders to the latest revision"
|
109
|
-
end
|
110
|
-
|
111
|
-
def self.detailed_help
|
112
|
-
<<EOF
|
113
|
-
usage: update [DIR [...]]
|
114
|
-
|
115
|
-
This operation has the effect of downloading all remote changes back to our
|
116
|
-
working copy. If any local modifications were done, they will be preserved.
|
117
|
-
If merge conflicts occur, they will not be taken care of, and your subsequent
|
118
|
-
commit will fail.
|
119
|
-
|
120
|
-
Piston will refuse to update a folder if it has pending updates. Run
|
121
|
-
'svn update' on the target folder to update it before running Piston
|
122
|
-
again.
|
123
|
-
EOF
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.aliases
|
127
|
-
%w(up)
|
128
|
-
end
|
33
|
+
def start(*args)
|
34
|
+
args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir|
|
35
|
+
begin
|
36
|
+
run(wcdir, options[:revision] || options[:commit] || :head)
|
37
|
+
rescue Piston::WorkingCopy::NotWorkingCopy
|
38
|
+
puts "#{wcdir} is not a working copy"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
129
42
|
end
|
130
43
|
end
|
131
44
|
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
|
19
|
+
def start(*args)
|
20
|
+
targets = args.flatten.map {|d| Pathname.new(d).expand_path}
|
21
|
+
run(targets)
|
22
|
+
puts "#{targets.length} directories upgraded"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
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
|