autoproj 1.12.6 → 1.13.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.
@@ -210,13 +210,108 @@ module Autoproj
210
210
  end
211
211
  end
212
212
 
213
+ # Returns true if packages and prefixes should be auto-generated, based
214
+ # on the SHA of the package names. This is meant to be used for build
215
+ # services that want to check that dependencies are properly set
216
+ #
217
+ # The default is false (disabled)
218
+ #
219
+ # @return [Boolean]
213
220
  def randomize_layout?
214
221
  get('randomize_layout', false)
215
222
  end
216
223
 
224
+ # Sets whether the layout should be randomized
225
+ #
226
+ # @return [Boolean]
227
+ # @see randomize_layout?
217
228
  def randomize_layout=(value)
218
229
  set('randomize_layout', value, true)
219
230
  end
231
+
232
+ DEFAULT_UTILITY_SETUP = Hash[
233
+ 'doc' => true,
234
+ 'test' => false]
235
+
236
+ # The configuration key that should be used to store the utility
237
+ # enable/disable information
238
+ #
239
+ # @param [String] the utility name
240
+ # @return [String] the config key
241
+ def utility_key(utility)
242
+ "autoproj_#{utility}_utility"
243
+ end
244
+
245
+ # Returns whether a given utility is enabled for the package
246
+ #
247
+ # If there is no specific configuration for the package, uses the global
248
+ # default set with utility_enable_all or utility_disable_all. If none of
249
+ # these methods has been called, uses the default in
250
+ # {DEFAULT_UTILITY_SETUP}
251
+ #
252
+ # @param [String] utility the utility name (e.g. 'doc' or 'test')
253
+ # @param [String] package the package name
254
+ # @return [Boolean] true if the utility should be enabled for the
255
+ # requested package and false otherwise
256
+ def utility_enabled_for?(utility, package)
257
+ utility_config = get(utility_key(utility), Hash.new)
258
+ if utility_config.has_key?(package)
259
+ utility_config[package]
260
+ else get("#{utility_key(utility)}_default", DEFAULT_UTILITY_SETUP[utility])
261
+ end
262
+ end
263
+
264
+ # Enables a utility for all packages
265
+ #
266
+ # This both sets the default value for all packages and resets all
267
+ # package-specific values set with {utility_enable_for} and
268
+ # {utility_disable_for}
269
+ #
270
+ # @param [String] utility the utility name (e.g. 'doc' or 'test')
271
+ # @return [void]
272
+ def utility_enable_all(utility)
273
+ reset(utility_key(utility))
274
+ set("#{utility_key(utility)}_default", true)
275
+ end
276
+
277
+ # Enables a utility for a specific package
278
+ #
279
+ # Note that if the default for this utility is to be enabled, this is
280
+ # essentially a no-op.
281
+ #
282
+ # @param [String] utility the utility name (e.g. 'doc' or 'test')
283
+ # @param [String] package the package name
284
+ # @return [void]
285
+ def utility_enable_for(utility, package)
286
+ utility_config = get(utility_key(utility), Hash.new)
287
+ set(utility_key(utility), utility_config.merge(package => true))
288
+ end
289
+
290
+ # Disables a utility for all packages
291
+ #
292
+ # This both sets the default value for all packages and resets all
293
+ # package-specific values set with {utility_enable_for} and
294
+ # {utility_disable_for}
295
+ #
296
+ # @param [String] utility the utility name (e.g. 'doc' or 'test')
297
+ # @return [void]
298
+ def utility_disable_all(utility)
299
+ reset(utility_key(utility))
300
+ set("#{utility_key(utility)}_default", false)
301
+ end
302
+
303
+ # Disables a utility for a specific package
304
+ #
305
+ # Note that if the default for this utility is to be disabled, this is
306
+ # essentially a no-op.
307
+ #
308
+ # @param [String] utility the utility name (e.g. 'doc' or 'test')
309
+ # @param [String] package the package name
310
+ # @return [void]
311
+ def utility_disable_for(utility, package)
312
+ utility_config = get(utility_key(utility), Hash.new)
313
+ set(utility_key(utility), utility_config.merge(package => false))
314
+ end
220
315
  end
221
316
  end
222
317
 
@@ -195,5 +195,8 @@ pip:
195
195
  opensuse: python-pip
196
196
  fedora: python-pip
197
197
 
198
+ sudo:
199
+ default: sudo
200
+
198
201
  # vim: expandtab
199
202
 
@@ -25,51 +25,35 @@ module Autoproj
25
25
  :http_url => "https://git.#{base_url}",
26
26
  :ssh_url => "git@#{base_url}:",
27
27
  :fallback_to_http => true,
28
- :default => 'http,ssh',
29
- :disabled_methods => []
30
-
31
- disabled_methods = Array(options[:disabled_methods])
28
+ :default => 'git,ssh'
29
+
30
+ gitorious_long_doc = [
31
+ "How should I interact with #{base_url} (git, http or ssh)",
32
+ "If you give two values, comma-separated, the first one will be",
33
+ "used for pulling and the second one for pushing"]
32
34
 
33
35
  access_methods = Hash[
34
36
  'git' => 'git,ssh',
35
37
  'ssh' => 'ssh,ssh',
36
38
  'http' => 'http,http']
37
39
 
38
- gitorious_long_doc = [
39
- "How should I interact with #{base_url} (#{(access_methods.keys - disabled_methods).sort.join(", ")})",
40
- "If you give two values, comma-separated, the first one will be",
41
- "used for pulling and the second one for pushing"]
42
-
43
- validator = lambda do |value|
44
- if value =~ /,/
45
- value.split(',').each do |method|
46
- if !access_methods.has_key?(method)
47
- raise Autoproj::InputError, "#{method} is not a known access method"
48
- elsif disabled_methods.include?(method)
49
- raise Autoproj::InputError, "#{method} is disabled on #{base_url}"
40
+ configuration_option name, 'string',
41
+ :default => options[:default],
42
+ :doc => gitorious_long_doc do |value|
43
+ if value =~ /,/
44
+ value.split(',').each do |method|
45
+ if !access_methods.has_key?(method)
46
+ raise Autoproj::InputError, "#{method} is not a known access method"
47
+ end
50
48
  end
49
+ elsif !access_methods.has_key?(value)
50
+ raise Autoproj::InputError, "#{value} is not a known access method"
51
51
  end
52
- elsif !access_methods.has_key?(value)
53
- raise Autoproj::InputError, "#{value} is not a known access method"
54
- elsif disabled_methods.include?(value)
55
- raise Autoproj::InputError, "#{method} is disabled on #{base_url}"
56
- end
57
-
58
- value
59
- end
60
52
 
61
- configuration_option name, 'string',
62
- :default => options[:default],
63
- :doc => gitorious_long_doc, &validator
53
+ value
54
+ end
64
55
 
65
- access_mode = Autoproj.config.get(name)
66
- begin
67
- validator[access_mode]
68
- rescue Autoproj::InputError => e
69
- Autoproj.warn e.message
70
- Autoproj.config.reset(name)
71
- access_mode = Autoproj.config.get(name)
72
- end
56
+ access_mode = Autoproj.user_config(name)
73
57
  access_mode = access_methods[access_mode] || access_mode
74
58
  pull, push = access_mode.split(',')
75
59
  [[pull, "_ROOT"], [push, "_PUSH_ROOT"]].each do |method, var_suffix|
@@ -89,11 +73,15 @@ module Autoproj
89
73
  end
90
74
  pull_base_url = Autoproj.user_config("#{name}_ROOT")
91
75
  push_base_url = Autoproj.user_config("#{name}_PUSH_ROOT")
92
- Hash[:type => 'git', :url => "#{pull_base_url}#{url}", :push_to => "#{push_base_url}#{url}", :retry_count => 10].merge(vcs_options)
76
+ Hash[type: 'git',
77
+ url: "#{pull_base_url}#{url}",
78
+ push_to: "#{push_base_url}#{url}",
79
+ retry_count: 10,
80
+ repository_id: "#{name.downcase}:#{url}"].merge(vcs_options)
93
81
  end
94
82
  end
95
83
  end
96
84
 
97
- Autoproj.gitorious_server_configuration('GITORIOUS', 'gitorious.org', :default => 'http,ssh', :disabled_methods => 'git')
85
+ Autoproj.gitorious_server_configuration('GITORIOUS', 'gitorious.org')
98
86
  Autoproj.gitorious_server_configuration('GITHUB', 'github.com', :http_url => 'https://github.com', :default => 'http,ssh')
99
87
 
@@ -40,7 +40,8 @@ module Autoproj
40
40
  # @return [Array<PackageSet>]
41
41
  attr_writer :package_sets
42
42
 
43
- # Returns true if +pkg_name+ has been explicitely selected
43
+ # Returns true if +pkg_name+ has been explicitely selected, either by
44
+ # the command line or through the layout
44
45
  def explicitly_selected_package?(pkg_name)
45
46
  explicit_selection && explicit_selection.include?(pkg_name)
46
47
  end
@@ -65,8 +66,14 @@ module Autoproj
65
66
 
66
67
  @file = file
67
68
  @data = data
68
- @ignored_packages |= (data['ignored_packages'] || Set.new)
69
- data['exclude_packages'] ||= Set.new
69
+ @ignored_packages |= (data['ignored_packages'] || Set.new).to_set
70
+ @manifest_exclusions |= (data['exclude_packages'] || Set.new).to_set
71
+
72
+ @normalized_layout = Hash.new
73
+ compute_normalized_layout(
74
+ normalized_layout,
75
+ '/',
76
+ data['layout'] || Hash.new)
70
77
 
71
78
  if data['constants']
72
79
  @constant_definitions = Autoproj.resolve_constant_definitions(data['constants'])
@@ -128,6 +135,7 @@ module Autoproj
128
135
  @ignored_os_dependencies = Set.new
129
136
  @reused_installations = Array.new
130
137
  @ignored_packages = Set.new
138
+ @manifest_exclusions = Set.new
131
139
 
132
140
  @constant_definitions = Hash.new
133
141
  if Autoproj.has_config_key?('manifest_source')
@@ -139,7 +147,7 @@ module Autoproj
139
147
  # Call this method to ignore a specific package. It must not be used in
140
148
  # init.rb, as the manifest is not yet loaded then
141
149
  def ignore_package(package_name)
142
- @ignored_packages << package_name
150
+ @ignored_packages << package_name.to_str
143
151
  end
144
152
 
145
153
  # True if the given package should not be built, with the packages that
@@ -185,7 +193,7 @@ module Autoproj
185
193
  # The set of package names that are listed in the excluded_packages
186
194
  # section of the manifest
187
195
  def manifest_exclusions
188
- data['exclude_packages']
196
+ @manifest_exclusions
189
197
  end
190
198
 
191
199
  # A package_name => reason map of the exclusions added with #add_exclusion.
@@ -198,6 +206,17 @@ module Autoproj
198
206
  automatic_exclusions[package_name] = reason
199
207
  end
200
208
 
209
+ # Tests whether the given package is excluded in the manifest
210
+ def excluded_in_manifest?(package_name)
211
+ manifest_exclusions.any? do |matcher|
212
+ if (pkg_set = metapackages[matcher]) && pkg_set.include?(package_name)
213
+ true
214
+ else
215
+ Regexp.new(matcher) === package_name
216
+ end
217
+ end
218
+ end
219
+
201
220
  # If +package_name+ is excluded from the build, returns a string that
202
221
  # tells why. Otherwise, returns nil
203
222
  #
@@ -205,7 +224,7 @@ module Autoproj
205
224
  # exclude_packages section of the manifest, or because they are
206
225
  # disabled on this particular operating system.
207
226
  def exclusion_reason(package_name)
208
- if manifest_exclusions.any? { |l| Regexp.new(l) =~ package_name }
227
+ if excluded_in_manifest?(package_name)
209
228
  "#{package_name} is listed in the exclude_packages section of the manifest"
210
229
  else
211
230
  automatic_exclusions[package_name]
@@ -218,7 +237,11 @@ module Autoproj
218
237
  # This is useful to avoid building packages that are of no use for the
219
238
  # user.
220
239
  def excluded?(package_name)
221
- if manifest_exclusions.any? { |l| Regexp.new(l) =~ package_name }
240
+ package_name = package_name.to_str
241
+
242
+ if normalized_layout.has_key?(package_name)
243
+ false
244
+ elsif excluded_in_manifest?(package_name)
222
245
  true
223
246
  elsif automatic_exclusions.any? { |pkg_name, | pkg_name == package_name }
224
247
  true
@@ -375,6 +398,12 @@ module Autoproj
375
398
  packages[name]
376
399
  end
377
400
 
401
+ def find_autobuild_package(name)
402
+ if pkg = packages[name]
403
+ pkg.autobuild
404
+ end
405
+ end
406
+
378
407
  def package(name)
379
408
  packages[name]
380
409
  end
@@ -500,6 +529,10 @@ module Autoproj
500
529
  set
501
530
  end
502
531
 
532
+ def main_package_set
533
+ each_package_set.find(&:main?)
534
+ end
535
+
503
536
  # Exception raised when a caller requires to use an excluded package
504
537
  class ExcludedPackage < ConfigError
505
538
  attr_reader :name
@@ -813,7 +846,11 @@ module Autoproj
813
846
  end
814
847
  end
815
848
 
816
- def normalized_layout(result = Hash.new, layout_level = '/', layout_data = (data['layout'] || Hash.new))
849
+ # A mapping from names to layout placement, as found in the layout
850
+ # section of the manifest
851
+ attr_reader :normalized_layout
852
+
853
+ def compute_normalized_layout(result, layout_level, layout_data)
817
854
  layout_data.each do |value|
818
855
  if value.kind_of?(Hash)
819
856
  subname, subdef = value.find { true }
@@ -877,7 +914,7 @@ module Autoproj
877
914
  pkg.autobuild.description = manifest
878
915
  package_manifests[package.name] = manifest
879
916
 
880
- manifest.each_dependency do |name, is_optional|
917
+ manifest.each_dependency(pkg.modes) do |name, is_optional|
881
918
  begin
882
919
  if is_optional
883
920
  package.optional_dependency name
@@ -37,7 +37,7 @@ module Autoproj
37
37
  FileUtils.mkdir_p File.dirname(pkg.importdir)
38
38
  Autobuild::Subprocess.run("autoproj-cache", "import", Autobuild.tool(:git), "--git-dir", pkg.importdir, 'init', "--bare")
39
39
  end
40
- pkg.importer.update_remotes_configuration(pkg, 'autoproj-cache')
40
+ pkg.importer.update_remotes_configuration(pkg)
41
41
 
42
42
  with_retry(10) do
43
43
  Autobuild::Subprocess.run('autoproj-cache', :import, Autobuild.tool('git'), '--git-dir', pkg.importdir, 'remote', 'update', 'autobuild')
@@ -96,7 +96,7 @@ module Autoproj
96
96
  fake_package.update = false
97
97
  end
98
98
  end
99
- fake_package.import(only_local)
99
+ fake_package.import(only_local: only_local)
100
100
 
101
101
  rescue Autobuild::ConfigException => e
102
102
  raise ConfigError.new, "cannot import #{name}: #{e.message}", e.backtrace
@@ -177,9 +177,11 @@ module Autoproj
177
177
  pkg_set
178
178
  end
179
179
 
180
- def queue_auto_imports_if_needed(queue, pkg_set)
180
+ def queue_auto_imports_if_needed(queue, pkg_set, root_set)
181
181
  if pkg_set.auto_imports?
182
182
  pkg_set.each_raw_imported_set do |import_vcs, import_options|
183
+ repository_id = repository_id_of(import_vcs)
184
+ import_vcs = root_set.overrides_for("pkg_set:#{repository_id}", import_vcs)
183
185
  queue << [import_vcs, import_options, pkg_set]
184
186
  end
185
187
  end
@@ -191,10 +193,7 @@ module Autoproj
191
193
  return "local:#{vcs.url}"
192
194
  end
193
195
 
194
- name = PackageSet.name_of(manifest, vcs)
195
- raw_local_dir = PackageSet.raw_local_dir_of(vcs)
196
- fake_package = Tools.create_autobuild_package(vcs, name, raw_local_dir)
197
- fake_package.importer.repository_id
196
+ vcs.create_autobuild_importer.repository_id
198
197
  end
199
198
 
200
199
  # Load the package set information
@@ -210,7 +209,7 @@ module Autoproj
210
209
  by_repository_id = Hash.new
211
210
  by_name = Hash.new
212
211
 
213
- queue = queue_auto_imports_if_needed(Array.new, root_pkg_set)
212
+ queue = queue_auto_imports_if_needed(Array.new, root_pkg_set, root_pkg_set)
214
213
  while !queue.empty?
215
214
  vcs, options, imported_from = queue.shift
216
215
  repository_id = repository_id_of(vcs)
@@ -258,7 +257,7 @@ module Autoproj
258
257
  by_name[pkg_set.name] = [pkg_set, vcs, options, imported_from]
259
258
 
260
259
  # Finally, queue the imports
261
- queue_auto_imports_if_needed(queue, pkg_set)
260
+ queue_auto_imports_if_needed(queue, pkg_set, root_pkg_set)
262
261
  end
263
262
 
264
263
  cleanup_remotes_dir(package_sets)
@@ -0,0 +1,258 @@
1
+ module Autoproj
2
+ module Ops
3
+ class Snapshot
4
+ # Update version control information with new choices
5
+ #
6
+ # The two parameters are formatted as expected in the version_control
7
+ # and overrides fields in source.yml / overrides.yml, that is (in YAML)
8
+ #
9
+ # - package_name:
10
+ # version: '10'
11
+ # control: '20'
12
+ # info: '30'
13
+ #
14
+ # The two parameters are expected to only use full package names, and
15
+ # not regular expressions
16
+ #
17
+ # @param [Array<String=>Hash>] overrides the information that should augment
18
+ # the current state
19
+ # @param [Array<String=>Hash>] state the current state
20
+ # @param [Hash] the updated information
21
+ def self.merge_packets( overrides, state )
22
+ result = overrides.dup
23
+ overriden = overrides.map { |entry| entry.keys.first }.to_set
24
+ state.each do |pkg|
25
+ name, _ = pkg.first
26
+ if !overriden.include?(name)
27
+ result << pkg
28
+ end
29
+ end
30
+ result
31
+ end
32
+
33
+ def sort_versions(versions)
34
+ pkg_sets, pkgs = versions.partition { |n, _| n =~ /^pkg_set:/ }
35
+ pkg_sets.sort_by { |n, _| n.keys.first } +
36
+ pkgs.sort_by { |n, _| n.keys.first }
37
+ end
38
+
39
+ def save_versions( versions, versions_file, options = Hash.new )
40
+ options = Kernel.validate_options options,
41
+ replace: false
42
+
43
+ existing_versions = Array.new
44
+ if !options[:replace] && File.exists?(versions_file)
45
+ existing_versions = YAML.load( File.read( versions_file ) ) ||
46
+ Array.new
47
+ end
48
+
49
+ # create direcotry for versions file first
50
+ FileUtils.mkdir_p(File.dirname( versions_file ))
51
+
52
+ # augment the versions file with the updated versions
53
+ Snapshot.merge_packets( versions, existing_versions )
54
+
55
+ versions = sort_versions(versions)
56
+
57
+ # write the yaml file
58
+ File.open(versions_file, 'w') do |io|
59
+ io.write YAML.dump(versions)
60
+ end
61
+ end
62
+
63
+ def self.snapshot( packages, target_dir )
64
+ # todo
65
+ end
66
+
67
+ attr_reader :manifest
68
+
69
+ # Control what happens if a package fails to be snapshotted
70
+ #
71
+ # If true, the failure to snapshot a package should lead to a warning.
72
+ # Otherwise (the default), it leads to an error.
73
+ #
74
+ # @return [Boolean]
75
+ # @see initialize error_or_warn
76
+ def keep_going?; !!@keep_going end
77
+
78
+ def initialize(manifest, options = Hash.new)
79
+ @manifest = manifest
80
+ options = Kernel.validate_options options,
81
+ keep_going: false
82
+ @keep_going = options[:keep_going]
83
+ end
84
+
85
+ def snapshot_package_sets(target_dir = nil)
86
+ result = Array.new
87
+ manifest.each_package_set do |pkg_set|
88
+ next if pkg_set.local?
89
+
90
+ if vcs_info = pkg_set.snapshot(target_dir)
91
+ result << Hash["pkg_set:#{pkg_set.repository_id}", vcs_info]
92
+ else
93
+ error_or_warn(pkg_set, "cannot snapshot #{package_name}: importer snapshot failed")
94
+ end
95
+ end
96
+ result
97
+ end
98
+
99
+ def error_or_warn(package, error_msg)
100
+ if keep_going?
101
+ Autoproj.warn error_msg
102
+ else
103
+ raise Autobuild::PackageException.new(package, 'snapshot'), error_msg
104
+ end
105
+ end
106
+
107
+ def snapshot_packages(packages, target_dir = nil)
108
+ result = Array.new
109
+ packages.each do |package_name|
110
+ package = manifest.packages[package_name]
111
+ if !package
112
+ raise ArgumentError, "#{package_name} is not a known package"
113
+ end
114
+ importer = package.autobuild.importer
115
+ if !importer
116
+ error_or_warn(package, "cannot snapshot #{package_name} as it has no importer")
117
+ next
118
+ elsif !importer.respond_to?(:snapshot)
119
+ error_or_warn(package, "cannot snapshot #{package_name} as the #{importer.class} importer does not support it")
120
+ next
121
+ end
122
+
123
+ vcs_info = importer.snapshot(package.autobuild, target_dir)
124
+ if vcs_info
125
+ result << Hash[package_name, vcs_info]
126
+ else
127
+ error_or_warn(package, "cannot snapshot #{package_name}: importer snapshot failed")
128
+ end
129
+ end
130
+ result
131
+ end
132
+
133
+ # Returns the list of existing version tags
134
+ def tags(package)
135
+ importer = package.importer
136
+ all_tags = importer.run_git_bare(package, 'tag')
137
+ all_tags.find_all do |tag_name|
138
+ end
139
+ end
140
+
141
+ # Returns a package that is used to store this installs import history
142
+ #
143
+ # Its importer is guaranteed to be a git importer
144
+ #
145
+ # @return [Autobuild::Package] a package whose importer is
146
+ # {Autobuild::Git}
147
+ def import_state_log_package
148
+ manifest.main_package_set.create_autobuild_package
149
+ end
150
+
151
+ def import_state_log_ref
152
+ "refs/autoproj"
153
+ end
154
+
155
+ DEFAULT_VERSIONS_FILE_BASENAME = "50-versions.yml"
156
+
157
+ def import_state_log_file
158
+ File.join(OVERRIDES_DIR, DEFAULT_VERSIONS_FILE_BASENAME)
159
+ end
160
+
161
+ def current_import_state
162
+ main = import_state_log_package
163
+ # Try to resolve the log ref, and extract the version file from it
164
+ begin
165
+ yaml = main.importer.show(main, import_state_log_ref, import_state_log_file)
166
+ YAML.load(yaml) || Array.new
167
+ rescue Autobuild::PackageException
168
+ Array.new
169
+ end
170
+ end
171
+
172
+ def update_package_import_state(name, packages)
173
+ current_versions = current_import_state
174
+ if current_versions.empty?
175
+ # Do a full snapshot this time only
176
+ Autoproj.message " building initial autoproj import log, this may take a while"
177
+ packages = manifest.all_selected_packages.
178
+ find_all { |pkg| File.directory?(manifest.find_package(pkg).autobuild.srcdir) }
179
+ end
180
+ versions = snapshot_package_sets
181
+ versions += snapshot_packages(packages)
182
+ versions = Snapshot.merge_packets(versions, current_versions)
183
+ save_import_state(name, versions)
184
+ end
185
+
186
+ def save_import_state(name, versions)
187
+ versions = sort_versions(versions)
188
+
189
+ main = import_state_log_package
190
+ git_dir = main.importer.git_dir(main, false)
191
+ # Ensure that our ref is being logged
192
+ FileUtils.touch File.join(git_dir, 'logs', *import_state_log_ref.split("/"))
193
+ # Create the commit with the versions info
194
+ commit_id = Snapshot.create_commit(main, import_state_log_file, name) do |io|
195
+ YAML.dump(versions, io)
196
+ end
197
+ # And save it in our reflog
198
+ main.importer.run_git_bare(main, "update-ref", '-m', name, import_state_log_ref, commit_id)
199
+ end
200
+
201
+ # Create a git commit in which a file contains provided content
202
+ #
203
+ # The target git repository's current index and history is left
204
+ # unmodified. The only modification is the creation of a new dangling
205
+ # commit.
206
+ #
207
+ # It creates a temporary file and gives it to the block so that the file
208
+ # gets filled with the new content
209
+ #
210
+ # @yieldparam [Tempfile] io a temporary file
211
+ # @param [Autobuild::Package] a package object whose importer is a git
212
+ # importer. The git commit is created in this repository
213
+ # @param [String] path the file to be created or updated, relative to
214
+ # the root of the git repository
215
+ # @param [String] the commit message
216
+ # @return [String] the commit ID
217
+ def self.create_commit(pkg, path, message)
218
+ importer = pkg.importer
219
+ object_id = Tempfile.open 'autoproj-versions' do |io|
220
+ yield(io)
221
+ io.flush
222
+ importer.run_git_bare(
223
+ pkg, 'hash-object', '-w',
224
+ '--path', path, io.path).first
225
+ end
226
+
227
+ cacheinfo = ["100644", object_id, path]
228
+ if Autobuild::Git.at_least_version(2, 1)
229
+ cacheinfo = cacheinfo.join(",")
230
+ end
231
+
232
+ # Create the tree using a temporary index in order to not mess with
233
+ # the user's index state. read-tree initializes the new index and
234
+ # then we add the overrides file with update-index / write-tree
235
+ our_index = File.join(importer.git_dir(pkg, false), 'index.autoproj')
236
+ FileUtils.rm_f our_index
237
+ begin
238
+ ENV['GIT_INDEX_FILE'] = our_index
239
+ importer.run_git_bare(pkg, 'read-tree', 'HEAD')
240
+ # And add the new file
241
+ importer.run_git_bare(
242
+ pkg, 'update-index',
243
+ '--add', '--cacheinfo', *cacheinfo)
244
+ tree_id = importer.run_git_bare(pkg, 'write-tree').first
245
+ ensure
246
+ ENV.delete('GIT_INDEX_FILE')
247
+ FileUtils.rm_f our_index
248
+ end
249
+
250
+ head_id = importer.rev_parse(pkg, 'HEAD')
251
+
252
+ importer.run_git_bare(
253
+ pkg, 'commit-tree',
254
+ tree_id, '-p', head_id, input_streams: [message]).first
255
+ end
256
+ end
257
+ end
258
+ end