piston 1.4.0 → 2.0.1
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 +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
|