autobuild 1.8.3 → 1.9.0.b1
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.
- checksums.yaml +4 -4
- data/Manifest.txt +16 -7
- data/Rakefile +2 -0
- data/lib/autobuild/config.rb +21 -6
- data/lib/autobuild/configurable.rb +2 -2
- data/lib/autobuild/environment.rb +52 -27
- data/lib/autobuild/exceptions.rb +48 -22
- data/lib/autobuild/import/archive.rb +37 -16
- data/lib/autobuild/import/cvs.rb +26 -28
- data/lib/autobuild/import/darcs.rb +9 -8
- data/lib/autobuild/import/git.rb +324 -217
- data/lib/autobuild/import/hg.rb +6 -9
- data/lib/autobuild/import/svn.rb +190 -47
- data/lib/autobuild/importer.rb +80 -35
- data/lib/autobuild/package.rb +16 -35
- data/lib/autobuild/packages/autotools.rb +8 -8
- data/lib/autobuild/packages/cmake.rb +18 -12
- data/lib/autobuild/packages/genom.rb +1 -1
- data/lib/autobuild/packages/gnumake.rb +11 -12
- data/lib/autobuild/packages/orogen.rb +1 -1
- data/lib/autobuild/packages/ruby.rb +9 -5
- data/lib/autobuild/reporting.rb +10 -6
- data/lib/autobuild/subcommand.rb +110 -50
- data/lib/autobuild/test.rb +104 -0
- data/lib/autobuild/timestamps.rb +3 -3
- data/lib/autobuild/tools.rb +1 -1
- data/lib/autobuild/utility.rb +22 -10
- data/lib/autobuild/version.rb +1 -1
- data/test/data/gitrepo-with-extra-commit-and-tag.tar +0 -0
- data/test/data/gitrepo.tar +0 -0
- data/test/data/gitrepo/test +0 -0
- data/test/data/gitrepo/test2 +0 -0
- data/test/data/gitrepo/test3 +0 -0
- data/test/data/svnroot.tar +0 -0
- data/test/import/test_cvs.rb +51 -0
- data/test/import/test_git.rb +364 -0
- data/test/import/test_svn.rb +144 -0
- data/test/import/test_tar.rb +76 -0
- data/test/suite.rb +7 -0
- data/test/test_config.rb +1 -5
- data/test/test_environment.rb +88 -0
- data/test/test_reporting.rb +2 -14
- data/test/test_subcommand.rb +7 -22
- metadata +17 -14
- data/test/test_import_cvs.rb +0 -59
- data/test/test_import_svn.rb +0 -56
- data/test/test_import_tar.rb +0 -83
- data/test/tools.rb +0 -44
data/lib/autobuild/import/cvs.rb
CHANGED
@@ -16,7 +16,6 @@ module Autobuild
|
|
16
16
|
raise ArgumentError, "no module given"
|
17
17
|
end
|
18
18
|
|
19
|
-
@program = Autobuild.tool('cvs')
|
20
19
|
@options_up = cvsopts[:cvsup] || '-dP'
|
21
20
|
@options_up = Array[*@options_up]
|
22
21
|
@options_co = cvsopts[:cvsco] || '-P'
|
@@ -34,31 +33,32 @@ module Autobuild
|
|
34
33
|
|
35
34
|
private
|
36
35
|
|
37
|
-
def update(package,
|
38
|
-
if only_local
|
39
|
-
|
36
|
+
def update(package, options = Hash.new) # :nodoc:
|
37
|
+
if options[:only_local]
|
38
|
+
package.warn "%s: the CVS importer does not support local updates, skipping"
|
40
39
|
return
|
41
40
|
end
|
42
|
-
Dir.chdir(package.srcdir) do
|
43
|
-
if !File.exists?("#{package.srcdir}/CVS/Root")
|
44
|
-
raise ConfigException.new(package, 'import'), "#{package.srcdir} is not a CVS working copy"
|
45
|
-
end
|
46
41
|
|
47
|
-
|
48
|
-
|
42
|
+
if !File.exist?("#{package.srcdir}/CVS/Root")
|
43
|
+
raise ConfigException.new(package, 'import'), "#{package.srcdir} is not a CVS working copy"
|
44
|
+
end
|
45
|
+
|
46
|
+
root = File.open("#{package.srcdir}/CVS/Root") { |io| io.read }.chomp
|
47
|
+
mod = File.open("#{package.srcdir}/CVS/Repository") { |io| io.read }.chomp
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
# Remove any :ext: in front of the root
|
50
|
+
root = root.gsub(/^:ext:/, '')
|
51
|
+
expected_root = @root.gsub(/^:ext:/, '')
|
52
|
+
# Remove the optional ':' between the host and the path
|
53
|
+
root = root.gsub(/:/, '')
|
54
|
+
expected_root = expected_root.gsub(/:/, '')
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
if root != expected_root || mod != @module
|
57
|
+
raise ConfigException.new(package, 'import'),
|
58
|
+
"checkout in #{package.srcdir} is from #{root}:#{mod}, was expecting #{expected_root}:#{@module}"
|
59
|
+
end
|
60
|
+
package.run(:import, Autobuild.tool(:cvs), 'up', *@options_up,
|
61
|
+
retry: true, working_directory: package.importdir)
|
62
62
|
end
|
63
63
|
|
64
64
|
def checkout(package) # :nodoc:
|
@@ -66,21 +66,19 @@ module Autobuild
|
|
66
66
|
cvsroot = @root
|
67
67
|
|
68
68
|
FileUtils.mkdir_p(head) if !File.directory?(head)
|
69
|
-
|
70
|
-
|
71
|
-
Subprocess.run(package, :import, *options)
|
72
|
-
end
|
69
|
+
package.run(:import, Autobuild.tool(:cvs), '-d', cvsroot, 'co', '-d', tail, *@options_co, modulename,
|
70
|
+
retry: true, working_directory: head)
|
73
71
|
end
|
74
72
|
end
|
75
73
|
|
76
74
|
# Returns the CVS importer which will get the +name+ module in repository
|
77
75
|
# +repo+. The allowed values in +options+ are described in CVSImporter.new.
|
78
|
-
def self.cvs(
|
76
|
+
def self.cvs(root, options = {}, backward_compatibility = nil)
|
79
77
|
if backward_compatibility
|
80
78
|
backward_compatibility[:module] = options
|
81
|
-
CVSImporter.new(
|
79
|
+
CVSImporter.new(root, backward_compatibility)
|
82
80
|
else
|
83
|
-
CVSImporter.new(
|
81
|
+
CVSImporter.new(root, options)
|
84
82
|
end
|
85
83
|
end
|
86
84
|
end
|
@@ -22,17 +22,18 @@ module Autobuild
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def update(package,
|
26
|
-
if only_local
|
27
|
-
|
25
|
+
def update(package, options = Hash.new) # :nodoc:
|
26
|
+
if options[:only_local]
|
27
|
+
package.warn "%s: the darcs importer does not support local updates, skipping"
|
28
28
|
return
|
29
29
|
end
|
30
30
|
if !File.directory?( File.join(package.srcdir, '_darcs') )
|
31
|
-
raise ConfigException.new(package, 'import'),
|
31
|
+
raise ConfigException.new(package, 'import'),
|
32
|
+
"#{package.srcdir} is not a Darcs repository"
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
-
'pull', '--all', "--repodir=#{package.srcdir}", '--set-scripts-executable', @source, *@pull)
|
35
|
+
package.run(:import, @program,
|
36
|
+
'pull', '--all', "--repodir=#{package.srcdir}", '--set-scripts-executable', @source, *@pull, retry: true)
|
36
37
|
end
|
37
38
|
|
38
39
|
def checkout(package) # :nodoc:
|
@@ -41,8 +42,8 @@ module Autobuild
|
|
41
42
|
FileUtils.mkdir_p(basedir)
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
-
'get', '--set-scripts-executable', @source, package.srcdir, *@get)
|
45
|
+
package.run(:import, @program,
|
46
|
+
'get', '--set-scripts-executable', @source, package.srcdir, *@get, retry: true)
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
data/lib/autobuild/import/git.rb
CHANGED
@@ -35,6 +35,46 @@ module Autobuild
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
# Returns the git version as a string
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
def self.version
|
42
|
+
version = Subprocess.run('git', 'setup', Autobuild.tool(:git), '--version').first
|
43
|
+
if version =~ /^git version (\d[\d\.]+)/
|
44
|
+
$1.split(".").map { |i| Integer(i) }
|
45
|
+
else
|
46
|
+
raise ArgumentError, "cannot parse git version string #{version}, was expecting something looking like 'git version 2.1.0'"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Helper method to compare two (partial) versions represented as array
|
51
|
+
# of integers
|
52
|
+
#
|
53
|
+
# @return [Integer] -1 if actual is greater than required,
|
54
|
+
# 0 if equal, and 1 if actual is smaller than required
|
55
|
+
def self.compare_versions(actual, required)
|
56
|
+
if actual.size > required.size
|
57
|
+
return -compare_versions(required, actual)
|
58
|
+
end
|
59
|
+
|
60
|
+
actual += [0] * (required.size - actual.size)
|
61
|
+
actual.zip(required).each do |v_act, v_req|
|
62
|
+
if v_act > v_req then return -1
|
63
|
+
elsif v_act < v_req then return 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
0
|
67
|
+
end
|
68
|
+
|
69
|
+
# Tests the git version
|
70
|
+
#
|
71
|
+
# @param [Array<Integer>] version the git version as an array of integer
|
72
|
+
# @return [Boolean] true if the git version is at least the requested
|
73
|
+
# one, and false otherwise
|
74
|
+
def self.at_least_version(*version)
|
75
|
+
compare_versions(self.version, version) <= 0
|
76
|
+
end
|
77
|
+
|
38
78
|
# Creates an importer which tracks the given repository
|
39
79
|
# and branch. +source+ is [repository, branch]
|
40
80
|
#
|
@@ -47,8 +87,7 @@ module Autobuild
|
|
47
87
|
@git_dir_cache = Array.new
|
48
88
|
|
49
89
|
if branch.respond_to?(:to_hash)
|
50
|
-
options = branch.to_hash
|
51
|
-
branch = nil
|
90
|
+
branch, options = nil, branch.to_hash
|
52
91
|
end
|
53
92
|
|
54
93
|
if branch
|
@@ -64,27 +103,19 @@ module Autobuild
|
|
64
103
|
branch: nil,
|
65
104
|
tag: nil,
|
66
105
|
commit: nil,
|
106
|
+
repository_id: nil,
|
107
|
+
source_id: nil,
|
67
108
|
with_submodules: false
|
68
109
|
if gitopts[:branch] && branch
|
69
110
|
raise ConfigException, "git branch specified with both the option hash and the explicit parameter"
|
70
111
|
end
|
71
|
-
|
72
|
-
sourceopts, common = Kernel.filter_options common,
|
73
|
-
:repository_id, :source_id
|
112
|
+
gitopts[:branch] ||= branch
|
74
113
|
|
75
114
|
super(common)
|
76
115
|
|
77
|
-
@
|
78
|
-
@with_submodules = gitopts[:with_submodules]
|
79
|
-
branch = gitopts[:branch] || branch
|
80
|
-
tag = gitopts[:tag]
|
81
|
-
commit = gitopts[:commit]
|
82
|
-
|
83
|
-
@branch = branch || 'master'
|
84
|
-
@tag = tag
|
85
|
-
@commit = commit
|
116
|
+
@with_submodules = gitopts.delete(:with_submodules)
|
86
117
|
@remote_name = 'autobuild'
|
87
|
-
relocate(repository,
|
118
|
+
relocate(repository, gitopts)
|
88
119
|
end
|
89
120
|
|
90
121
|
# The name of the remote that should be set up by the importer
|
@@ -159,12 +190,12 @@ module Autobuild
|
|
159
190
|
# The tag we are pointing to. It is a tag name.
|
160
191
|
#
|
161
192
|
# If set, both branch and commit have to be nil.
|
162
|
-
|
193
|
+
attr_accessor :tag
|
163
194
|
|
164
195
|
# The commit we are pointing to. It is a commit ID.
|
165
196
|
#
|
166
197
|
# If set, both branch and tag have to be nil.
|
167
|
-
|
198
|
+
attr_accessor :commit
|
168
199
|
|
169
200
|
# True if it is allowed to merge remote updates automatically. If false
|
170
201
|
# (the default), the import will fail if the updates do not resolve as
|
@@ -192,7 +223,7 @@ module Autobuild
|
|
192
223
|
# :bare or :normal, or nil if path is not a git repository.
|
193
224
|
def self.resolve_git_dir(path)
|
194
225
|
dir = File.join(path, '.git')
|
195
|
-
if !File.
|
226
|
+
if !File.exist?(dir)
|
196
227
|
dir = path
|
197
228
|
end
|
198
229
|
|
@@ -213,74 +244,108 @@ module Autobuild
|
|
213
244
|
end
|
214
245
|
|
215
246
|
@git_dir_cache = [package.importdir, dir, style]
|
247
|
+
self.class.validate_git_dir(package, require_working_copy, dir, style)
|
248
|
+
dir
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.git_dir(package, require_working_copy)
|
252
|
+
dir, style = Git.resolve_git_dir(package.importdir)
|
253
|
+
validate_git_dir(package, require_working_copy, dir, style)
|
254
|
+
dir
|
255
|
+
end
|
256
|
+
|
257
|
+
# Validates the return value of {resolve_git_dir}
|
258
|
+
#
|
259
|
+
# @param [Package] package the package we are working on
|
260
|
+
# @param [Boolean] require_working_copy if false, a bare repository will
|
261
|
+
# be considered as valid, otherwise not
|
262
|
+
# @param [String,nil] dir the path to the repository's git directory, or nil
|
263
|
+
# if the target is not a valid repository (see the documentation of
|
264
|
+
# {resolve_git_dir}
|
265
|
+
# @param [Symbol,nil] style either :normal for a git checkout with
|
266
|
+
# working copy, :bare for a bare repository or nil if {resolve_git_dir}
|
267
|
+
# did not detect a git repository
|
268
|
+
#
|
269
|
+
# @return [void]
|
270
|
+
# @raise ConfigException if dir/style are nil, or if
|
271
|
+
# require_working_copy is true and style is :bare
|
272
|
+
def self.validate_git_dir(package, require_working_copy, dir, style)
|
216
273
|
if !style
|
217
|
-
raise ConfigException.new(package, 'import'
|
274
|
+
raise ConfigException.new(package, 'import', retry: false),
|
275
|
+
"while importing #{package.name}, #{package.importdir} does not point to a git repository"
|
218
276
|
elsif require_working_copy && (style == :bare)
|
219
|
-
raise ConfigException.new(package, 'import'
|
220
|
-
|
221
|
-
return dir
|
277
|
+
raise ConfigException.new(package, 'import', retry: false),
|
278
|
+
"while importing #{package.name}, #{package.importdir} points to a bare git repository but a working copy was required"
|
222
279
|
end
|
223
280
|
end
|
224
281
|
|
225
282
|
# Computes the merge status for this package between two existing tags
|
226
283
|
# Raises if a tag is unknown
|
227
284
|
def delta_between_tags(package, from_tag, to_tag)
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
285
|
+
pkg_tags = tags(package)
|
286
|
+
if not pkg_tags.has_key?(from_tag)
|
287
|
+
raise ArgumentError, "tag '#{from_tag}' is unknown to #{package.name} -- known tags are: #{pkg_tags.keys}"
|
288
|
+
end
|
289
|
+
if not pkg_tags.has_key?(to_tag)
|
290
|
+
raise ArgumentError, "tag '#{to_tag}' is unknown to #{package.name} -- known tags are: #{pkg_tags.keys}"
|
291
|
+
end
|
236
292
|
|
237
|
-
|
238
|
-
|
293
|
+
from_commit = pkg_tags[from_tag]
|
294
|
+
to_commit = pkg_tags[to_tag]
|
239
295
|
|
240
|
-
|
241
|
-
end
|
296
|
+
merge_status(package, to_commit, from_commit)
|
242
297
|
end
|
243
298
|
|
244
299
|
# Retrieve the tags of this packages as a hash mapping to the commit id
|
245
300
|
def tags(package)
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
301
|
+
run_git_bare(package, 'fetch', '--tags')
|
302
|
+
tag_list = run_git_bare(package, 'show-ref', '--tags').map(&:strip)
|
303
|
+
tags = Hash.new
|
304
|
+
tag_list.each do |entry|
|
305
|
+
commit_to_tag = entry.split(" ")
|
306
|
+
tags[commit_to_tag[1].sub("refs/tags/","")] = commit_to_tag[0]
|
307
|
+
end
|
308
|
+
tags
|
309
|
+
end
|
310
|
+
|
311
|
+
def run_git(package, *args)
|
312
|
+
self.class.run_git(package, *args)
|
313
|
+
end
|
314
|
+
|
315
|
+
def self.run_git(package, *args)
|
316
|
+
options = Hash.new
|
317
|
+
if args.last.kind_of?(Hash)
|
318
|
+
options = args.pop
|
256
319
|
end
|
320
|
+
|
321
|
+
working_directory = File.dirname(git_dir(package, true))
|
322
|
+
package.run(:import, Autobuild.tool(:git), *args,
|
323
|
+
Hash[working_directory: working_directory].merge(options))
|
324
|
+
end
|
325
|
+
|
326
|
+
def run_git_bare(package, *args)
|
327
|
+
self.class.run_git_bare(package, *args)
|
257
328
|
end
|
258
329
|
|
259
|
-
def
|
260
|
-
|
261
|
-
Subprocess.run(*git, "remote.#{remote_name}.url", repository)
|
262
|
-
Subprocess.run(*git, "remote.#{remote_name}.fetch", "+refs/heads/*:refs/remotes/#{remote_name}/*")
|
263
|
-
Subprocess.run(*git, 'fetch', '--tags', remote_name)
|
330
|
+
def self.run_git_bare(package, *args)
|
331
|
+
package.run(:import, Autobuild.tool(:git), '--git-dir', git_dir(package, false), *args)
|
264
332
|
end
|
265
333
|
|
266
334
|
# Updates the git repository's configuration for the target remote
|
267
|
-
def update_remotes_configuration(package
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
Subprocess.run(*git, "remote.#{remote_name}.pushurl", push_to)
|
272
|
-
end
|
273
|
-
Subprocess.run(*git, "remote.#{remote_name}.fetch", "+refs/heads/*:refs/remotes/#{remote_name}/*")
|
335
|
+
def update_remotes_configuration(package)
|
336
|
+
run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.url", repository)
|
337
|
+
run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.pushurl", push_to || repository)
|
338
|
+
run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.fetch", "+refs/heads/*:refs/remotes/#{remote_name}/*")
|
274
339
|
|
275
340
|
if remote_branch && local_branch
|
276
|
-
|
341
|
+
run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.push", "refs/heads/#{local_branch}:refs/heads/#{remote_branch}")
|
277
342
|
else
|
278
|
-
|
343
|
+
run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.push", "refs/heads/*:refs/heads/*")
|
279
344
|
end
|
280
345
|
|
281
346
|
if local_branch
|
282
|
-
|
283
|
-
|
347
|
+
run_git_bare(package, 'config', '--replace-all', "branch.#{local_branch}.remote", remote_name)
|
348
|
+
run_git_bare(package, 'config', '--replace-all', "branch.#{local_branch}.merge", "refs/heads/#{local_branch}")
|
284
349
|
end
|
285
350
|
end
|
286
351
|
|
@@ -290,7 +355,6 @@ module Autobuild
|
|
290
355
|
def fetch_remote(package)
|
291
356
|
validate_importdir(package)
|
292
357
|
git_dir = git_dir(package, false)
|
293
|
-
git = [package, :import, Autobuild.tool('git'), '--git-dir', git_dir]
|
294
358
|
|
295
359
|
# If we are checking out a specific commit, we don't know which
|
296
360
|
# branch to refer to in git fetch. So, we have to set up the
|
@@ -303,9 +367,9 @@ module Autobuild
|
|
303
367
|
# configuration parameters only if the repository and branch are
|
304
368
|
# OK (i.e. we keep old working configuration instead)
|
305
369
|
refspec = [branch || tag].compact
|
306
|
-
|
370
|
+
run_git_bare(package, 'fetch', '--tags', repository, *refspec, retry: true)
|
307
371
|
|
308
|
-
update_remotes_configuration(package
|
372
|
+
update_remotes_configuration(package)
|
309
373
|
|
310
374
|
# Now get the actual commit ID from the FETCH_HEAD file, and
|
311
375
|
# return it
|
@@ -319,85 +383,117 @@ module Autobuild
|
|
319
383
|
|
320
384
|
# Update the remote tag if needs be
|
321
385
|
if branch && commit_id
|
322
|
-
|
323
|
-
"-m", "updated by autobuild", "refs/remotes/#{remote_name}/#{remote_branch}", commit_id)
|
386
|
+
run_git_bare(package, 'update-ref', "-m", "updated by autobuild", "refs/remotes/#{remote_name}/#{remote_branch}", commit_id)
|
324
387
|
end
|
325
388
|
|
326
389
|
commit_id
|
327
390
|
end
|
328
391
|
|
329
392
|
def self.has_uncommitted_changes?(package, with_untracked_files = false)
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
393
|
+
status = run_git(package, 'status', '--porcelain').map(&:strip)
|
394
|
+
if with_untracked_files
|
395
|
+
!status.empty?
|
396
|
+
else
|
397
|
+
status.any? { |l| l[0, 2] !~ /^\?\?|^ / }
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Returns the commit ID of what we should consider being the remote
|
402
|
+
# commit
|
403
|
+
#
|
404
|
+
# @param [Package] package
|
405
|
+
# @param [Boolean] only_local if true, no remote access should be
|
406
|
+
# performed, in which case the current known state of the remote will be
|
407
|
+
# used. If false, we access the remote repository to fetch the actual
|
408
|
+
# commit ID
|
409
|
+
# @return [String] the commit ID as a string
|
410
|
+
def current_remote_commit(package, only_local = false)
|
411
|
+
if only_local
|
412
|
+
begin
|
413
|
+
run_git_bare(package, 'show-ref', '-s', "refs/remotes/#{remote_name}/#{remote_branch}").first.strip
|
414
|
+
rescue SubcommandFailed
|
415
|
+
raise PackageException.new(package, "import"), "cannot resolve remote HEAD #{remote_name}/#{remote_branch}"
|
416
|
+
end
|
417
|
+
else
|
418
|
+
begin fetch_remote(package)
|
419
|
+
rescue Exception => e
|
420
|
+
return fallback(e, package, :status, package, only_local)
|
336
421
|
end
|
337
422
|
end
|
338
423
|
end
|
339
424
|
|
425
|
+
|
340
426
|
# Returns a Importer::Status object that represents the status of this
|
341
427
|
# package w.r.t. the root repository
|
342
428
|
def status(package, only_local = false)
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
remote_commit =
|
350
|
-
begin fetch_remote(package)
|
351
|
-
rescue Exception => e
|
352
|
-
return fallback(e, package, :status, package, only_local)
|
353
|
-
end
|
354
|
-
|
355
|
-
if !remote_commit
|
356
|
-
return
|
357
|
-
end
|
358
|
-
end
|
429
|
+
validate_importdir(package)
|
430
|
+
remote_commit = current_remote_commit(package, only_local)
|
431
|
+
status = merge_status(package, remote_commit)
|
432
|
+
status.uncommitted_code = self.class.has_uncommitted_changes?(package)
|
433
|
+
status
|
434
|
+
end
|
359
435
|
|
360
|
-
|
361
|
-
|
362
|
-
|
436
|
+
def has_commit?(package, commit_id)
|
437
|
+
run_git_bare(package, 'rev-parse', '-q', '--verify', "#{commit_id}^{commit}")
|
438
|
+
true
|
439
|
+
rescue SubcommandFailed => e
|
440
|
+
if e.status == 1
|
441
|
+
false
|
442
|
+
else raise
|
363
443
|
end
|
444
|
+
end
|
364
445
|
|
446
|
+
def has_branch?(package, branch_name)
|
447
|
+
run_git_bare(package, 'show-ref', '-q', '--verify', "refs/heads/#{branch_name}")
|
448
|
+
true
|
449
|
+
rescue SubcommandFailed => e
|
450
|
+
if e.status == 1
|
451
|
+
false
|
452
|
+
else raise
|
453
|
+
end
|
365
454
|
end
|
366
455
|
|
367
|
-
def has_local_branch?
|
368
|
-
|
369
|
-
$?.exitstatus == 0
|
456
|
+
def has_local_branch?(package)
|
457
|
+
has_branch?(package, local_branch)
|
370
458
|
end
|
371
459
|
|
372
|
-
def detached_head?
|
373
|
-
|
374
|
-
return ($?.exitstatus != 0)
|
460
|
+
def detached_head?(package)
|
461
|
+
current_branch(package).nil?
|
375
462
|
end
|
376
463
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
464
|
+
# Returns the branch HEAD is pointing to
|
465
|
+
#
|
466
|
+
# @return [String,nil] the full ref HEAD is pointing to (i.e.
|
467
|
+
# refs/heads/master), or nil if HEAD is detached
|
468
|
+
# @raises SubcommandFailed if git failed
|
469
|
+
def current_branch(package)
|
470
|
+
run_git_bare(package, 'symbolic-ref', 'HEAD', '-q').first.strip
|
471
|
+
rescue SubcommandFailed => e
|
472
|
+
if e.status == 1
|
382
473
|
return
|
474
|
+
else raise
|
383
475
|
end
|
384
|
-
return current_branch
|
385
476
|
end
|
386
477
|
|
387
478
|
# Checks if the current branch is the target branch. Expects that the
|
388
479
|
# current directory is the package's directory
|
389
|
-
def
|
390
|
-
if current_branch = self.current_branch
|
480
|
+
def on_local_branch?(package)
|
481
|
+
if current_branch = self.current_branch(package)
|
391
482
|
current_branch == "refs/heads/#{local_branch}"
|
392
483
|
end
|
393
484
|
end
|
394
485
|
|
486
|
+
# @deprecated use on_local_branch? instead
|
487
|
+
def on_target_branch?(package)
|
488
|
+
on_local_branch?(package)
|
489
|
+
end
|
490
|
+
|
395
491
|
class Status < Importer::Status
|
396
492
|
attr_reader :fetch_commit
|
397
493
|
attr_reader :head_commit
|
398
494
|
attr_reader :common_commit
|
399
495
|
|
400
|
-
def initialize(status, remote_commit, local_commit, common_commit)
|
496
|
+
def initialize(package, status, remote_commit, local_commit, common_commit)
|
401
497
|
super()
|
402
498
|
@status = status
|
403
499
|
@fetch_commit = fetch_commit
|
@@ -405,10 +501,10 @@ module Autobuild
|
|
405
501
|
@common_commit = common_commit
|
406
502
|
|
407
503
|
if remote_commit != common_commit
|
408
|
-
@remote_commits = log(common_commit, remote_commit)
|
504
|
+
@remote_commits = log(package, common_commit, remote_commit)
|
409
505
|
end
|
410
506
|
if local_commit != common_commit
|
411
|
-
@local_commits = log(common_commit, local_commit)
|
507
|
+
@local_commits = log(package, common_commit, local_commit)
|
412
508
|
end
|
413
509
|
end
|
414
510
|
|
@@ -416,26 +512,42 @@ module Autobuild
|
|
416
512
|
status == Status::NEEDS_MERGE || status == Status::SIMPLE_UPDATE
|
417
513
|
end
|
418
514
|
|
419
|
-
def log(from, to)
|
420
|
-
log =
|
421
|
-
|
422
|
-
|
423
|
-
log = log.encode
|
424
|
-
end
|
425
|
-
|
426
|
-
encodings = ['UTF-8', 'iso8859-1']
|
427
|
-
begin
|
428
|
-
log.split("\n")
|
429
|
-
rescue
|
430
|
-
if encodings.empty?
|
431
|
-
return "[some log messages have invalid characters, cannot display/parse them]"
|
432
|
-
end
|
433
|
-
log.force_encoding(encodings.pop)
|
434
|
-
retry
|
515
|
+
def log(package, from, to)
|
516
|
+
log = package.importer.run_git_bare(package, 'log', '--encoding=UTF-8', "--pretty=format:%h %cr %cn %s", "#{from}..#{to}")
|
517
|
+
log.map do |line|
|
518
|
+
line.strip.encode
|
435
519
|
end
|
436
520
|
end
|
437
521
|
end
|
438
522
|
|
523
|
+
def rev_parse(package, name)
|
524
|
+
run_git_bare(package, 'rev-parse', name).first
|
525
|
+
rescue Autobuild::SubcommandFailed
|
526
|
+
raise PackageException.new(package, 'import'), "failed to resolve #{name}. Are you sure this commit, branch or tag exists ?"
|
527
|
+
end
|
528
|
+
|
529
|
+
def show(package, commit, path)
|
530
|
+
run_git_bare(package, 'show', "#{commit}:#{path}").join("\n")
|
531
|
+
rescue Autobuild::SubcommandFailed
|
532
|
+
raise PackageException.new(package, 'import'), "failed to either resolve commit #{commit} or file #{path}"
|
533
|
+
end
|
534
|
+
|
535
|
+
# Tests whether a commit is already present in a given history
|
536
|
+
#
|
537
|
+
# @param [Package] the package we are working on
|
538
|
+
# @param [String] commit the commit ID we want to verify the presence of
|
539
|
+
# @param [String] reference the reference commit. The method tests that
|
540
|
+
# 'commit' is present in the history of 'reference'
|
541
|
+
#
|
542
|
+
# @return [Boolean]
|
543
|
+
def commit_present_in?(package, commit, reference)
|
544
|
+
merge_base = run_git_bare(package, 'merge-base', commit, reference).first
|
545
|
+
merge_base == commit
|
546
|
+
|
547
|
+
rescue Exception
|
548
|
+
raise PackageException.new(package, 'import'), "failed to find the merge-base between #{commit} and #{reference}. Are you sure these commits exist ?"
|
549
|
+
end
|
550
|
+
|
439
551
|
# Computes the update status to update a branch whose tip is at
|
440
552
|
# reference_commit (which can be a symbolic reference) using the
|
441
553
|
# fetch_commit commit
|
@@ -446,16 +558,15 @@ module Autobuild
|
|
446
558
|
# git merge fetch_commit
|
447
559
|
#
|
448
560
|
def merge_status(package, fetch_commit, reference_commit = "HEAD")
|
449
|
-
|
450
|
-
|
561
|
+
begin
|
562
|
+
common_commit = run_git_bare(package, 'merge-base', reference_commit, fetch_commit).first.strip
|
563
|
+
rescue Exception
|
451
564
|
raise PackageException.new(package, 'import'), "failed to find the merge-base between #{reference_commit} and #{fetch_commit}. Are you sure these commits exist ?"
|
452
565
|
end
|
453
|
-
|
454
|
-
|
455
|
-
raise PackageException.new(package, 'import'), "failed to resolve #{reference_commit}. Are you sure this commit, branch or tag exists ?"
|
456
|
-
end
|
566
|
+
remote_commit = rev_parse(package, fetch_commit)
|
567
|
+
head_commit = rev_parse(package, reference_commit)
|
457
568
|
|
458
|
-
status = if common_commit !=
|
569
|
+
status = if common_commit != remote_commit
|
459
570
|
if common_commit == head_commit
|
460
571
|
Status::SIMPLE_UPDATE
|
461
572
|
else
|
@@ -469,7 +580,7 @@ module Autobuild
|
|
469
580
|
end
|
470
581
|
end
|
471
582
|
|
472
|
-
Status.new(status, fetch_commit, head_commit, common_commit)
|
583
|
+
Status.new(package, status, fetch_commit, head_commit, common_commit)
|
473
584
|
end
|
474
585
|
|
475
586
|
# Updates the git alternates file in the already checked out package to
|
@@ -506,78 +617,96 @@ module Autobuild
|
|
506
617
|
end
|
507
618
|
end
|
508
619
|
|
509
|
-
def
|
620
|
+
def commit_pinning(package, target_commit, fetch_commit)
|
621
|
+
current_head = rev_parse(package, 'HEAD')
|
622
|
+
|
623
|
+
# Check whether the current HEAD is present on the remote
|
624
|
+
# repository. We'll refuse resetting if there are uncommitted
|
625
|
+
# changes
|
626
|
+
head_to_remote = merge_status(package, fetch_commit, current_head)
|
627
|
+
status_to_remote = head_to_remote.status
|
628
|
+
if status_to_remote == Status::ADVANCED || status_to_remote == Status::NEEDS_MERGE
|
629
|
+
raise ImporterCannotReset.new(package, 'import'), "branch #{local_branch} of #{package.name} contains commits that do not seem to be present on the branch #{remote_branch} of the remote repository. I can't go on as it could make you loose some stuff. Update the remote branch in your overrides, push your changes or reset to the remote commit manually before trying again"
|
630
|
+
end
|
631
|
+
|
632
|
+
head_to_target = merge_status(package, target_commit, current_head)
|
633
|
+
status_to_target = head_to_target.status
|
634
|
+
if status_to_target == Status::UP_TO_DATE
|
635
|
+
return
|
636
|
+
end
|
637
|
+
|
638
|
+
package.message " %%s: resetting branch %s to %s" % [local_branch, target_commit.to_s]
|
639
|
+
# I don't use a reset --hard here as it would add even more
|
640
|
+
# restrictions on when we can do the operation (as we would refuse
|
641
|
+
# doing it if there are local changes). The checkout creates a
|
642
|
+
# detached HEAD, but makes sure that applying uncommitted changes is
|
643
|
+
# fine (it would abort otherwise). The rest then updates HEAD and
|
644
|
+
# the local_branch ref to match the required target commit
|
645
|
+
resolved_target_commit = rev_parse(package, "#{target_commit}^{commit}")
|
646
|
+
begin
|
647
|
+
run_git(package, 'checkout', target_commit)
|
648
|
+
run_git(package, 'update-ref', "refs/heads/#{local_branch}", resolved_target_commit)
|
649
|
+
run_git(package, 'symbolic-ref', "HEAD", "refs/heads/#{local_branch}")
|
650
|
+
rescue ::Exception
|
651
|
+
run_git(package, 'symbolic-ref', "HEAD", target_commit)
|
652
|
+
run_git(package, 'update-ref', "refs/heads/#{local_branch}", current_head)
|
653
|
+
run_git(package, 'checkout', local_branch)
|
654
|
+
raise
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
# @option (see Package#update)
|
659
|
+
def update(package, options = Hash.new)
|
510
660
|
validate_importdir(package)
|
661
|
+
|
511
662
|
# This is really really a hack to workaround how broken the
|
512
663
|
# importdir thing is
|
513
664
|
if package.importdir == package.srcdir
|
514
665
|
update_alternates(package)
|
515
666
|
end
|
516
|
-
Dir.chdir(package.importdir) do
|
517
|
-
#Checking if we should only merge our repro to remotes/HEAD without updateing from the remote side...
|
518
|
-
if !only_local
|
519
|
-
fetch_commit = fetch_remote(package)
|
520
|
-
end
|
521
667
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
if detached_head?
|
530
|
-
status_to_remote = merge_status(package, target_commit, fetch_commit)
|
531
|
-
if status_to_remote.status != Status::UP_TO_DATE
|
532
|
-
package.message " the package is on a detached HEAD because of commit pinning"
|
533
|
-
return
|
534
|
-
end
|
535
|
-
else
|
536
|
-
return
|
537
|
-
end
|
538
|
-
elsif status_to_head.status != Status::SIMPLE_UPDATE
|
539
|
-
raise PackageException.new(package, 'import'), "checking out the specified commit #{target_commit} would be a non-simple operation (i.e. the current state of the repository is not a linear relationship with the specified commit), do it manually"
|
540
|
-
end
|
541
|
-
|
542
|
-
status_to_remote = merge_status(package, target_commit, fetch_commit)
|
543
|
-
if status_to_remote.status != Status::UP_TO_DATE
|
544
|
-
# Try very hard to avoid creating a detached HEAD
|
545
|
-
if local_branch
|
546
|
-
status_to_branch = merge_status(package, target_commit, local_branch)
|
547
|
-
if status_to_branch.status == Status::UP_TO_DATE # Checkout the branch
|
548
|
-
package.message " checking out specific commit %s for %s. It will checkout branch %s." % [target_commit.to_s, package.name, local_branch]
|
549
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'checkout', local_branch)
|
550
|
-
return
|
551
|
-
end
|
552
|
-
end
|
553
|
-
package.message " checking out specific commit %s for %s. This will create a detached HEAD." % [target_commit.to_s, package.name]
|
554
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'checkout', target_commit)
|
668
|
+
# Check whether we are already at the requested state
|
669
|
+
pinned_state = (commit || tag)
|
670
|
+
if pinned_state && has_commit?(package, pinned_state)
|
671
|
+
pinned_state = rev_parse(package, pinned_state)
|
672
|
+
current_head = rev_parse(package, 'HEAD')
|
673
|
+
if options[:reset]
|
674
|
+
if current_head == pinned_state
|
555
675
|
return
|
556
676
|
end
|
557
|
-
|
558
|
-
|
559
|
-
if !fetch_commit
|
677
|
+
elsif commit_present_in?(package, pinned_state, current_head)
|
560
678
|
return
|
561
679
|
end
|
680
|
+
end
|
681
|
+
fetch_commit = current_remote_commit(package, options[:only_local])
|
562
682
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
package.message " switching branch of %s to %s" % [package.name, local_branch]
|
568
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'checkout', local_branch)
|
569
|
-
else
|
570
|
-
package.message " checking out branch %s for %s" % [local_branch, package.name]
|
571
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'checkout', '-b', local_branch, "FETCH_HEAD")
|
572
|
-
end
|
683
|
+
target_commit =
|
684
|
+
if commit then commit
|
685
|
+
elsif tag then "refs/tags/#{tag}"
|
686
|
+
else fetch_commit
|
573
687
|
end
|
574
688
|
|
575
|
-
|
689
|
+
# If we are tracking a commit/tag, just check it out and return
|
690
|
+
if !has_local_branch?(package)
|
691
|
+
package.message "%%s: checking out branch %s" % [local_branch]
|
692
|
+
run_git(package, 'checkout', '-b', local_branch, target_commit)
|
693
|
+
return
|
694
|
+
end
|
695
|
+
|
696
|
+
if !on_target_branch?(package)
|
697
|
+
package.message "%%s: switching to branch %s" % [local_branch]
|
698
|
+
run_git(package, 'checkout', local_branch)
|
699
|
+
end
|
700
|
+
|
701
|
+
if options[:reset]
|
702
|
+
commit_pinning(package, target_commit, fetch_commit)
|
703
|
+
else
|
704
|
+
status = merge_status(package, target_commit)
|
576
705
|
if status.needs_update?
|
577
706
|
if !merge? && status.status == Status::NEEDS_MERGE
|
578
707
|
raise PackageException.new(package, 'import'), "the local branch '#{local_branch}' and the remote branch #{branch} of #{package.name} have diverged, and I therefore refuse to update automatically. Go into #{package.importdir} and either reset the local branch or merge the remote changes"
|
579
708
|
end
|
580
|
-
|
709
|
+
run_git(package, 'merge', target_commit)
|
581
710
|
end
|
582
711
|
end
|
583
712
|
end
|
@@ -600,49 +729,27 @@ module Autobuild
|
|
600
729
|
FileUtils.mkdir_p base_dir
|
601
730
|
end
|
602
731
|
|
603
|
-
clone_options =
|
604
|
-
|
732
|
+
clone_options = Array.new
|
605
733
|
if with_submodules?
|
606
734
|
clone_options << '--recurse-submodules'
|
607
735
|
end
|
608
736
|
each_alternate_path(package) do |path|
|
609
737
|
clone_options << '--reference' << path
|
610
738
|
end
|
611
|
-
|
612
|
-
Autobuild.tool('git'), 'clone', *clone_options, repository, package.importdir)
|
613
|
-
|
614
|
-
Dir.chdir(package.importdir) do
|
615
|
-
if push_to
|
616
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'config',
|
617
|
-
"--replace-all", "remote.#{remote_name}.pushurl", push_to)
|
618
|
-
end
|
619
|
-
if local_branch && remote_branch
|
620
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'config',
|
621
|
-
"--replace-all", "remote.#{remote_name}.push", "refs/heads/#{local_branch}:refs/heads/#{remote_branch}")
|
622
|
-
end
|
739
|
+
package.run(:import,
|
740
|
+
Autobuild.tool('git'), 'clone', '-o', remote_name, *clone_options, repository, package.importdir, retry: true)
|
623
741
|
|
624
|
-
|
625
|
-
|
626
|
-
status = merge_status(package, commit || tag)
|
627
|
-
if status.status != Status::UP_TO_DATE
|
628
|
-
package.message " checking out specific commit for %s. This will create a detached HEAD." % [package.name]
|
629
|
-
Subprocess.run(package, :import, Autobuild.tool('git'), 'checkout', commit || tag)
|
630
|
-
end
|
631
|
-
else
|
632
|
-
current_branch = `git symbolic-ref HEAD`.chomp
|
633
|
-
if current_branch == "refs/heads/#{local_branch}"
|
634
|
-
Subprocess.run(package, :import, Autobuild.tool('git'),
|
635
|
-
'reset', '--hard', "#{remote_name}/#{branch}")
|
636
|
-
else
|
637
|
-
Subprocess.run(package, :import, Autobuild.tool('git'),
|
638
|
-
'checkout', '-b', local_branch, "#{remote_name}/#{branch}")
|
639
|
-
end
|
640
|
-
end
|
641
|
-
end
|
742
|
+
update_remotes_configuration(package)
|
743
|
+
update(package, only_local: true)
|
642
744
|
end
|
643
745
|
|
644
746
|
# Changes the repository this importer is pointing to
|
645
747
|
def relocate(repository, options = Hash.new)
|
748
|
+
@push_to = options[:push_to] || @push_to
|
749
|
+
@branch = options[:branch] || @branch || 'master'
|
750
|
+
@tag = options[:tag] || @tag
|
751
|
+
@commit = options[:commit] || @commit
|
752
|
+
|
646
753
|
@repository = repository.to_str
|
647
754
|
@repository_id = options[:repository_id] ||
|
648
755
|
"git:#{@repository}"
|