autobuild 1.8.3 → 1.9.0.b1
Sign up to get free protection for your applications and to get access to all the features.
- 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}"
|