motomike-bnr_tools 0.0.2
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/VERSION.yml +4 -0
- data/bin/taggit +29 -0
- data/lib/bnr_tools.rb +6 -0
- data/lib/bnr_tools/changeset.rb +60 -0
- data/lib/bnr_tools/digg_module.rb +103 -0
- data/lib/bnr_tools/mergeinator.rb +129 -0
- data/lib/bnr_tools/svn_commands.rb +264 -0
- data/lib/bnr_tools/svn_xml.rb +316 -0
- data/lib/bnr_tools/ticket.rb +52 -0
- data/lib/bnr_tools/trollop.rb +714 -0
- data/lib/bnr_tools/version_number.rb +138 -0
- data/lib/highline.rb +744 -0
- data/lib/highline/color_scheme.rb +120 -0
- data/lib/highline/import.rb +43 -0
- data/lib/highline/menu.rb +395 -0
- data/lib/highline/question.rb +462 -0
- data/lib/highline/system_extensions.rb +125 -0
- data/lib/open4.rb +393 -0
- data/test/bnr_tools_test.rb +47 -0
- data/test/test_helper.rb +85 -0
- metadata +75 -0
data/VERSION.yml
ADDED
data/bin/taggit
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
begin
|
3
|
+
require 'rubygems'
|
4
|
+
gem 'bnr_tools'
|
5
|
+
rescue LoadError
|
6
|
+
$: << File.expand_path(File.dirname(__FILE__)) + "/../lib"
|
7
|
+
require 'bnr_tools'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'mergeinator'
|
11
|
+
require 'ticket'
|
12
|
+
require 'trollop'
|
13
|
+
|
14
|
+
CURRENT_RELEASE_BRANCH="9.4"
|
15
|
+
|
16
|
+
opts = Trollop::options do
|
17
|
+
opt :ticket, "A ticket number optionally followed by one or more changesets to merge", :type => :ints, :multi => true
|
18
|
+
opt :wc_root_path, "Root path to a working copy of the repository we can do merge gymnastics in", :default => "#{ENV["HOME"]}/diggRepository"
|
19
|
+
opt :release_branch, "Name of the release branch which will be the merge target", :default => CURRENT_RELEASE_BRANCH, :required => true, :multi => true
|
20
|
+
end
|
21
|
+
|
22
|
+
tickets = Set.new
|
23
|
+
Array(opts[:release_branch]).each { |release_branch|
|
24
|
+
opts[:ticket].each { |ticketRefAndChangesets|
|
25
|
+
ticket, *changesets = *ticketRefAndChangesets
|
26
|
+
tickets << Ticket.new(ticket, changesets, opts[:wc_root_path], release_branch)
|
27
|
+
}
|
28
|
+
Mergeinator.new(opts[:wc_root_path], release_branch, tickets).mergeStuff
|
29
|
+
}
|
data/lib/bnr_tools.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'svn_commands'
|
2
|
+
require 'digg_module'
|
3
|
+
|
4
|
+
class Changeset
|
5
|
+
include Comparable
|
6
|
+
attr_reader :revision
|
7
|
+
attr_accessor :wcBasePath, :releaseBranch
|
8
|
+
def initialize(rev, wc_base_path=SvnCommands::DIGG_DEFAULT_WC_PATH, release_branch="9.4")
|
9
|
+
@revision = Integer(rev)
|
10
|
+
@modified = {}
|
11
|
+
@affectedModules = {}
|
12
|
+
self.wcBasePath=wc_base_path
|
13
|
+
self.releaseBranch=release_branch
|
14
|
+
end
|
15
|
+
|
16
|
+
def modifiedPaths(relativeStartPath = self.wcBasePath)
|
17
|
+
@modified[relativeStartPath] ||= begin
|
18
|
+
SvnCommands::Log.new(relativeStartPath,"-r #{self.revision}", true).entries.first.paths.map {|p| p.to_s}
|
19
|
+
rescue
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def affectedModulesbak(modulePath = "LOLz/trunk")
|
25
|
+
@affectedModules[modulePath] ||= self.modifiedPaths(modulePath).inject(Set.new) { |memo, modifiedPath|
|
26
|
+
(modifiedPath =~ /#{modulePath}\/([^\/]*)/) ? memo << DiggModule.new($1, self.wcBasePath, self.releaseBranch) : memo
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def affectedModules(modulePath = "LOLz/trunk")
|
31
|
+
@affectedModules[modulePath] ||= begin
|
32
|
+
self.modifiedPaths(modulePath).inject(Set.new) { |memo, modifiedPath|
|
33
|
+
if modifiedPath =~ /#{modulePath}\/([^\/]*)/
|
34
|
+
moduleName = $1
|
35
|
+
unless (memo.detect{ |m| m.moduleName == moduleName })
|
36
|
+
memo << DiggModule.new($1, self.wcBasePath, self.releaseBranch)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
memo
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
"r#{revision}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def <=>(other)
|
49
|
+
self.revision <=> other.revision
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash
|
53
|
+
self.revision.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql?(other)
|
57
|
+
self.revision == other.revision
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'svn_commands'
|
2
|
+
require 'version_number'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class DiggModule
|
6
|
+
include Comparable
|
7
|
+
attr_reader :moduleName, :wcBasePath, :releaseBranch
|
8
|
+
def initialize(module_name, wc_base_path, release_branch)
|
9
|
+
@moduleName = module_name
|
10
|
+
@wcBasePath = wc_base_path
|
11
|
+
@releaseBranch = release_branch
|
12
|
+
end
|
13
|
+
|
14
|
+
def utilDir
|
15
|
+
@utilDir ||= begin
|
16
|
+
provisionalDir = "#{self.wcBasePath}/build/trunk"
|
17
|
+
unless File.exist? provisionalDir
|
18
|
+
SvnCommands::Update.new("build/trunk", nil, true, false, self.wcBasePath) # update the current working copy to be sure the dir is there
|
19
|
+
end
|
20
|
+
provisionalDir
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def myCatDir
|
25
|
+
"#{self.wcBasePath}/LOLcat/branches/#{self.releaseBranch}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def myDir
|
29
|
+
"#{self.myCatDir}/modules/#{self.moduleName}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def getVersion
|
33
|
+
Dir.chdir(myDir) { |cwd|
|
34
|
+
VersionNumber.new(IO.popen(%Q{ #{self.utilDir}/version package.xml }).readlines.first.strip)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def updateVersion
|
39
|
+
Dir.chdir(myDir) { |cwd|
|
40
|
+
current_version = self.getVersion
|
41
|
+
new_version = block_given?() ? (yield current_version) : (current_version.next_after_patchlevel_increment)
|
42
|
+
puts IO.popen(%Q{ #{self.utilDir}/setversion package.xml #{new_version} }).readlines
|
43
|
+
self.updateManifest("#{myCatDir}/manifest/pear-common",new_version)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def updateManifest(manifest_path,new_version)
|
48
|
+
manifest_lines = File.readlines(manifest_path)
|
49
|
+
File.open(manifest_path, "w+") { |f|
|
50
|
+
manifest_lines.each { |line|
|
51
|
+
if line =~ /^pear.digg.internal\/#{self.moduleName}-/
|
52
|
+
f.puts("pear.digg.internal/#{self.moduleName}-#{new_version}")
|
53
|
+
else
|
54
|
+
f.puts(line)
|
55
|
+
end
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def updateContents
|
61
|
+
Dir.chdir(myDir) { |cwd|
|
62
|
+
puts IO.popen(%Q{ #{self.utilDir}/update-contents package.xml }).readlines
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def updateStability(stability="stable")
|
67
|
+
Dir.chdir(myDir) { |cwd|
|
68
|
+
puts IO.popen(%Q{ #{self.utilDir}/setstability package.xml #{stability} }).readlines
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def buildPearPackage
|
73
|
+
Dir.chdir(myDir) { |cwd|
|
74
|
+
puts IO.popen(%Q{ pear package }).readlines
|
75
|
+
}
|
76
|
+
%Q{ #{self.utilDir}/upload #{myDir}/#{self.moduleName}-#{self.getVersion}.tgz }
|
77
|
+
end
|
78
|
+
|
79
|
+
def cleanupPackageXmlFormatting
|
80
|
+
Dir.chdir(myDir) { |cwd|
|
81
|
+
FileUtils.copy("package.xml","package.xml.unformatted")
|
82
|
+
puts IO.popen(%Q{ XMLLINT_INDENT=" " xmllint --format package.xml.unformatted > package.xml }).readlines
|
83
|
+
FileUtils.rm("package.xml.unformatted")
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
self.moduleName
|
89
|
+
end
|
90
|
+
|
91
|
+
def <=>(other)
|
92
|
+
self.moduleName <=> other.moduleName
|
93
|
+
end
|
94
|
+
|
95
|
+
def eql?(other)
|
96
|
+
self.moduleName == other.moduleName
|
97
|
+
end
|
98
|
+
|
99
|
+
def hash
|
100
|
+
self.moduleName.hash
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'svn_commands'
|
2
|
+
require 'version_number'
|
3
|
+
require 'highline/import'
|
4
|
+
require 'set'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
class Mergeinator
|
8
|
+
attr_reader :wcPath, :releaseBranchVersion
|
9
|
+
attr_accessor :catPath, :modulePath, :ticketsToMerge
|
10
|
+
|
11
|
+
def initialize(wc_path = SvnCommands::DIGG_DEFAULT_WC_PATH, release = VersionNumber.new(CURRENT_RELEASE_BRANCH,false), tickets=Set.new )
|
12
|
+
@wcPath = wc_path
|
13
|
+
@releaseBranchVersion = release.kind_of?(VersionNumber) ? release : VersionNumber.new(release,false)
|
14
|
+
self.ticketsToMerge = Set.new(Array(tickets))
|
15
|
+
self.catPath = "LOLcat/branches/#{releaseBranch}"
|
16
|
+
self.modulePath = "#{self.catPath}/modules"
|
17
|
+
end
|
18
|
+
|
19
|
+
def releaseBranch
|
20
|
+
"#{self.releaseBranchVersion.major}.#{self.releaseBranchVersion.minor}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def branchWorkingDir
|
24
|
+
"#{self.wcPath}/LOLcat/branches/#{self.releaseBranch}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def version
|
28
|
+
@versionToTag ||= begin
|
29
|
+
preExistingVersions = []
|
30
|
+
SvnCommands::Ls.new("LOLcat/tags").entries.each { |entry|
|
31
|
+
begin
|
32
|
+
entryVersion = VersionNumber.new(entry.name)
|
33
|
+
preExistingVersions << entryVersion
|
34
|
+
rescue Exception => e
|
35
|
+
puts "Exception processing entry: #{e}"
|
36
|
+
end
|
37
|
+
}
|
38
|
+
candidateVersions = preExistingVersions.find_all { |version| version.major == self.releaseBranchVersion.major && version.minor == self.releaseBranchVersion.minor }
|
39
|
+
highestCandidateVersion = candidateVersions.sort.last
|
40
|
+
puts "Highest existing version: #{highestCandidateVersion}"
|
41
|
+
ask("LOL{z,cat} version to tag?") { |q|
|
42
|
+
q.default = highestCandidateVersion.next_after_patchlevel_increment.to_s
|
43
|
+
q.validate = /#{self.releaseBranchVersion.major}\.#{self.releaseBranchVersion.minor}\.\d+/
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def tagLOLCat
|
49
|
+
ref_msg = self.ticketsToMerge ? "Refs #{(self.ticketsToMerge.map {|t| "#{t}"}).join(", ")}" : "No tickets referenced"
|
50
|
+
if agree("References: #{ref_msg}\nOkay to tag branch #{self.releaseBranch} as version #{self.version}?")
|
51
|
+
SvnCommands.copy("LOLcat/branches/#{self.releaseBranch}","LOLcat/tags/#{version}", nil, nil, "Tagged LOLcat-#{version}. #{ref_msg}")
|
52
|
+
SvnCommands.copy("LOLz/branches/#{self.releaseBranch}","LOLz/tags/#{version}", nil, nil, "Tagged LOLz-#{version}. #{ref_msg}")
|
53
|
+
else
|
54
|
+
exit(1) unless agree("Oh. Well, would you like to keep going anyway, even though we can't commit anything?")
|
55
|
+
end
|
56
|
+
SvnCommands::Update.new("LOLcat/tags/#{version}",nil,true,true,self.wcPath) # shallowly checkout the new tag to ~/diggRepository/, no extra args for svn, force update
|
57
|
+
t = Tempfile.new("svn_externals").open { |tempfile|
|
58
|
+
tempfile.puts "modules svn+ssh://svn.digg.internal/repository/LOLz/tags/#{version}"
|
59
|
+
tempfile.puts "build svn+ssh://svn.digg.internal/repository/build/trunk"
|
60
|
+
}
|
61
|
+
cmd = "svn ps svn:externals --file #{t.path} #{SvnCommands::DIGG_DEFAULT_WC_PATH}/LOLcat/tags/#{version}"
|
62
|
+
puts "Running command : #{cmd}"
|
63
|
+
puts IO.popen(cmd).readlines
|
64
|
+
end
|
65
|
+
|
66
|
+
def svnMergeCommand(project, changeset)
|
67
|
+
"svn merge -c #{changeset.revision} svn+ssh://svn.digg.internal/repository/#{project}/trunk"
|
68
|
+
end
|
69
|
+
|
70
|
+
def mergeStuff
|
71
|
+
modules = Set.new
|
72
|
+
allChangesets = Set.new
|
73
|
+
self.cleanWorkingCopy
|
74
|
+
ticketsToMerge.each { |ticket|
|
75
|
+
allChangesets.merge(ticket.changesets)
|
76
|
+
modules.merge(ticket.affectedModules)
|
77
|
+
}
|
78
|
+
puts "Affected modules: #{modules.to_a.join(", ")}."
|
79
|
+
commitMessage = "Refs #{(ticketsToMerge.sort.map {|ticket| ticket.to_s(false)}).join(", ")} : Merged #{(allChangesets.sort.map {|c| c.to_s }).join(", ")} from trunk"
|
80
|
+
puts "Commit message = #{commitMessage}"
|
81
|
+
allChangesets.sort.each { |changeset|
|
82
|
+
# We could check paths and such, but that's just an extra step for the cat ...
|
83
|
+
SvnCommands.merge("LOLcat/trunk",self.catPath,changeset.revision,nil,nil,self.wcPath)
|
84
|
+
unless changeset.affectedModules("LOLz/trunk").empty? # OTOH, for modules, we already cached the information
|
85
|
+
SvnCommands.merge("LOLz/trunk",self.modulePath,changeset.revision,nil,nil,self.wcPath)
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
@module_upload_cmds = []
|
90
|
+
modules.each { |m|
|
91
|
+
m.updateVersion
|
92
|
+
m.updateContents
|
93
|
+
m.updateStability("stable")
|
94
|
+
m.cleanupPackageXmlFormatting
|
95
|
+
@module_upload_cmds << m.buildPearPackage
|
96
|
+
}
|
97
|
+
|
98
|
+
# Sanity check - spew the entire diff before committing it
|
99
|
+
Dir.chdir(self.branchWorkingDir) { |cwd|
|
100
|
+
puts IO.popen(%Q{ svn st && svn diff . modules }).readlines
|
101
|
+
@module_upload_cmds.each { |cmd| puts cmd }
|
102
|
+
puts "Before I commit - does everything look okay?"
|
103
|
+
if agree("Commit message: #{commitMessage}")
|
104
|
+
system %Q{ svn st | awk '/^\s?[MAD]/ { print $NF } ' | xargs svn commit -m'#{commitMessage}' }
|
105
|
+
else
|
106
|
+
exit(1) unless agree("Oh. Well, would you like to keep going anyway, even though we can't commit anything?")
|
107
|
+
end
|
108
|
+
}
|
109
|
+
tagLOLCat
|
110
|
+
if agree("Upload modified modules now?")
|
111
|
+
@module_upload_cmds.each { |cmd|
|
112
|
+
puts IO.popen(cmd).readlines
|
113
|
+
}
|
114
|
+
else
|
115
|
+
exit(1) unless agree("Oh. Well, would you like to keep going anyway, even though we can't commit anything?")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def cleanWorkingCopy(force=false)
|
120
|
+
if (force || agree("Clean working copy now?")) then
|
121
|
+
SvnCommands::Update.new(self.catPath, nil, true, false, self.wcPath) # update the current working copy to be sure the dir is there
|
122
|
+
Dir.chdir(branchWorkingDir) {
|
123
|
+
puts IO.popen(%Q{ svn revert -R . && svn revert -R modules && svn stat | grep "^\?" | cut -c8- | xargs rm -rf }).readlines
|
124
|
+
}
|
125
|
+
else
|
126
|
+
puts "Okay, trusting you ... it's all going to end in tears, I just know it ..."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'lib/bnr_tools'
|
5
|
+
rescue
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'svn_xml'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'open4'
|
11
|
+
|
12
|
+
module SvnCommands
|
13
|
+
DIGG_DEFAULT_SVN_URL="svn+ssh://svn.digg.internal/repository"
|
14
|
+
DIGG_DEFAULT_WC_PATH="#{ENV['HOME']}/diggRepository"
|
15
|
+
|
16
|
+
class SvnException < RuntimeError
|
17
|
+
def initialize(status_code, output)
|
18
|
+
@status_code = status_code
|
19
|
+
@output = output
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"Status code:#{@status_code} / output:#{@output} #{super}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def svnVersion
|
29
|
+
@@svnVersion ||= VersionNumber.new(`svn --version --quiet`)
|
30
|
+
end
|
31
|
+
|
32
|
+
def extra_merge_flags
|
33
|
+
(Integer(svnVersion.major) >= 1 && Integer(svnVersion.minor) >= 5) ? ["--accept postpone"] : []
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_cmd(command_string, expected_result_class=nil, max_retries=3)
|
37
|
+
puts "Executing: '#{command_string}'"
|
38
|
+
retry_count = 0
|
39
|
+
begin
|
40
|
+
out = ""
|
41
|
+
err = ""
|
42
|
+
status = Open4::popen4(command_string) do |pid, stdin, stdout, stderr|
|
43
|
+
stdin.close
|
44
|
+
out = stdout.read
|
45
|
+
err = stderr.read
|
46
|
+
end
|
47
|
+
unless status.exitstatus == 0
|
48
|
+
raise SvnException.new(status.exitstatus, "stdout: #{out}\n\nstderr: #{err}")
|
49
|
+
end
|
50
|
+
if (expected_result_class)
|
51
|
+
parsed = REXML::Document.new(out)
|
52
|
+
expected_result_class.new(parsed.elements[expected_result_class.expected_node_name])
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
rescue Exception => e
|
57
|
+
if e.kind_of?(Interrupt)
|
58
|
+
exit(1)
|
59
|
+
elsif retry_count < max_retries
|
60
|
+
# puts "invocation failed with #{e.class} #{e} ... trying again"
|
61
|
+
retry_count += 1
|
62
|
+
retry
|
63
|
+
else
|
64
|
+
raise e
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def copy(fromPath, toPath, extra_args=nil, working_copy_path=nil, commit_msg=nil, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
70
|
+
path_base = working_copy_path || repository_base_url
|
71
|
+
full_from_path = "#{path_base.chomp("/")}/#{fromPath}"
|
72
|
+
full_to_path = "#{path_base.chomp("/")}/#{toPath}"
|
73
|
+
if (working_copy_path)
|
74
|
+
SvnCommands.new_cmd("svn cp #{Array(extra_args).join(" ")} \"#{full_from_path}\" \"#{full_to_path}\"",nil,1)
|
75
|
+
else
|
76
|
+
if (commit_msg)
|
77
|
+
SvnCommands.new_cmd("svn cp #{Array(extra_args).join(" ")} \"#{full_from_path}\" \"#{full_to_path}\" -m\"#{commit_msg}\"",nil,1)
|
78
|
+
else
|
79
|
+
raise SvnException.new(-1, "Without a commit message, I can't svn cp a remote URL. Sorry.")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def merge(fromPath, toPath, start_rev, end_rev = nil, extra_args=nil, working_copy_path=nil, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
85
|
+
extra_args = (Array(extra_args) << SvnCommands.extra_merge_flags).compact
|
86
|
+
full_from_path = "#{repository_base_url.chomp("/")}/#{fromPath}"
|
87
|
+
full_to_path = "#{working_copy_path.chomp("/")}/#{toPath}"
|
88
|
+
rev_spec = begin
|
89
|
+
if end_rev.nil?() || Integer(end_rev) == 0
|
90
|
+
"-c #{Integer(start_rev)}"
|
91
|
+
else
|
92
|
+
"-r#{Integer(start_rev)}:#{Integer(end_rev)}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
SvnCommands.new_cmd("svn merge #{rev_spec} #{extra_args.join(" ")} \"#{full_from_path}\" \"#{full_to_path}\"")
|
96
|
+
end
|
97
|
+
|
98
|
+
def sync(fromPath, toPath, working_copy_path, extra_args=nil, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
99
|
+
extra_args = (Array(extra_args) << SvnCommands.extra_merge_flags).compact
|
100
|
+
repository_from_path = "#{repository_base_url.chomp("/")}/#{fromPath}"
|
101
|
+
repository_to_path = "#{repository_base_url.chomp("/")}/#{toPath}"
|
102
|
+
wc_to_path = "#{working_copy_path.chomp("/")}/#{toPath}"
|
103
|
+
SvnCommands.new_cmd("svn merge #{extra_args.join(" ")} \"#{repository_to_path}\" \"#{repository_from_path}\" \"#{wc_to_path}\"")
|
104
|
+
end
|
105
|
+
|
106
|
+
def mkdir(path, commit_msg=nil, working_copy_path=nil, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
107
|
+
path_base = working_copy_path || repository_base_url
|
108
|
+
full_path = "#{path_base.chomp("/")}/#{path}"
|
109
|
+
extra_args = (Array(extra_args) << SvnCommands.extra_merge_flags).compact
|
110
|
+
if (working_copy_path)
|
111
|
+
SvnCommands.new_cmd("svn mkdir #{full_path}")
|
112
|
+
else
|
113
|
+
if (commit_msg)
|
114
|
+
SvnCommands.new_cmd("svn mkdir #{full_path} -m'#{commit_msg}'")
|
115
|
+
else
|
116
|
+
raise SvnException.new(-1, "No commit message specified; cannot mkdir directly in the repository")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
class PathOp
|
124
|
+
attr_accessor :fullpath, :entries
|
125
|
+
def initialize(relativePath, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
126
|
+
@fullpath=relativePath.nil? ? repository_base_url : "#{repository_base_url}/#{relativePath}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Ls < PathOp
|
131
|
+
attr_accessor :lists
|
132
|
+
@@all_lists ||= {}
|
133
|
+
def initialize(relativePath=nil, extra_args=nil, force_update=false, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
134
|
+
super(relativePath, repository_base_url)
|
135
|
+
cmd = "svn ls --xml #{Array(extra_args).join(" ")} #{fullpath}"
|
136
|
+
@@all_lists[fullpath] = if (force_update || @@all_lists[fullpath].nil?)
|
137
|
+
SvnCommands.new_cmd(cmd, SvnXml::Lists)
|
138
|
+
else
|
139
|
+
@@all_lists[fullpath]
|
140
|
+
end
|
141
|
+
@lists = @@all_lists[fullpath]
|
142
|
+
@entries = @lists.entries
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Log < PathOp
|
147
|
+
attr_reader :entries
|
148
|
+
@@all_entries ||= {}
|
149
|
+
def initialize(relativePath=nil, extra_args=nil, force_update=false, repository_base_url=DIGG_DEFAULT_SVN_URL)
|
150
|
+
super(relativePath, repository_base_url)
|
151
|
+
@@all_entries[fullpath] = if (force_update || @@all_entries[fullpath].nil?)
|
152
|
+
SvnCommands.new_cmd("svn log -vv --xml #{Array(extra_args).join(" ")} #{fullpath}", SvnXml::Log).entries
|
153
|
+
else
|
154
|
+
@@all_entries[fullpath]
|
155
|
+
end
|
156
|
+
@entries = @@all_entries[fullpath]
|
157
|
+
end
|
158
|
+
|
159
|
+
def messages
|
160
|
+
@messages ||= self.entries.collect { |entry| entry.msg }
|
161
|
+
end
|
162
|
+
|
163
|
+
def revisions
|
164
|
+
@revisions ||= self.entries.collect { |entry| entry.revision }
|
165
|
+
end
|
166
|
+
|
167
|
+
def oldest_revision
|
168
|
+
self.revisions.sort.first
|
169
|
+
end
|
170
|
+
|
171
|
+
def newest_revision
|
172
|
+
self.revisions.sort.last
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class Update < PathOp
|
177
|
+
@@shallow_paths ||= {}
|
178
|
+
@@recursive_paths ||= {}
|
179
|
+
|
180
|
+
def updatedShallow?(path)
|
181
|
+
path_parent = File.dirname(path)
|
182
|
+
if [".","/",""].include?(path_parent)
|
183
|
+
@@shallow_paths[path] || false
|
184
|
+
else
|
185
|
+
@@shallow_paths[path] || updatedRecursive?(path_parent)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def updatedRecursive?(path)
|
190
|
+
path_parent = File.dirname(path)
|
191
|
+
if [".","/",""].include? path_parent
|
192
|
+
@@recursive_paths[path] || false
|
193
|
+
else
|
194
|
+
@@recursive_paths[path] || updatedRecursive?(path_parent)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def alreadyUpdated?(shallowUpdate)
|
199
|
+
if shallowUpdate
|
200
|
+
updatedShallow?(fullpath)
|
201
|
+
else
|
202
|
+
updatedRecursive?(fullpath)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def pretty_hash(h)
|
207
|
+
if h.kind_of?(Hash)
|
208
|
+
h.inject(nil) { |memo, kv|
|
209
|
+
key = kv[0]
|
210
|
+
val = kv[1]
|
211
|
+
(memo ? "#{memo}; " : "") + "#{key}=#{pretty_hash(val)}"
|
212
|
+
}
|
213
|
+
else
|
214
|
+
h.to_s
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def updateCache(shallow)
|
219
|
+
if shallow
|
220
|
+
@@shallow_paths[fullpath] ||= 0
|
221
|
+
@@shallow_paths[fullpath] += 1
|
222
|
+
#puts "Updated shallow cache; current contents #{pretty_hash(@@shallow_paths)}"
|
223
|
+
else
|
224
|
+
@@recursive_paths[fullpath] ||= 0
|
225
|
+
@@recursive_paths[fullpath] += 1
|
226
|
+
#puts "Updated recursive cache; current contents #{pretty_hash(@@recursive_paths)}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def ensureWorkingCopyExistsAt(aPath)
|
231
|
+
out = `svn info #{aPath}`
|
232
|
+
unless $? == 0
|
233
|
+
FileUtils.mkdir_p(aPath)
|
234
|
+
SvnCommands.new_cmd("svn co -N svn+ssh://svn.digg.internal/repository/ #{aPath.chomp("/")}")
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def initialize(path, extra_args=nil, force_update=false, shallow=false, working_copy_path=DIGG_DEFAULT_WC_PATH)
|
239
|
+
super(path, working_copy_path)
|
240
|
+
cache_hit = alreadyUpdated?(shallow)
|
241
|
+
#puts "Update requested on #{path} - cache hit = #{cache_hit}"
|
242
|
+
if force_update || cache_hit == false || cache_hit.nil?
|
243
|
+
argPile = Array(extra_args)
|
244
|
+
if (shallow)
|
245
|
+
argPile << "-N"
|
246
|
+
end
|
247
|
+
argPile << fullpath
|
248
|
+
if (path == nil || path.size < 2)
|
249
|
+
ensureWorkingCopyExistsAt(working_copy_path)
|
250
|
+
else
|
251
|
+
wc_fullpath = "#{working_copy_path}/#{path}"
|
252
|
+
unless (File.exist? wc_fullpath)
|
253
|
+
# make sure our parent dir is in good shape ...
|
254
|
+
#puts "Hm, #{fullpath} doesn't exist. Calling checkOut (shallow) on #{File.dirname(relative_path)} ..."
|
255
|
+
SvnCommands::Update.new(File.dirname(path), nil, force_update, true, working_copy_path)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
SvnCommands.new_cmd("svn up #{argPile.join(" ")}")
|
259
|
+
end
|
260
|
+
updateCache(shallow)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|