Class: Chef::Provider::Git

Inherits:
Chef::Provider show all
Extended by:
Forwardable
Defined in:
lib/chef/provider/git.rb

Constant Summary collapse

GIT_VERSION_PATTERN =
Regexp.compile('git version (\d+\.\d+.\d+)')

Instance Attribute Summary

Attributes inherited from Chef::Provider

#action, #after_resource, #current_resource, #logger, #new_resource, #recipe_name, #run_context

Instance Method Summary collapse

Methods inherited from Chef::Provider

action, #action_nothing, #check_resource_semantics!, #cleanup_after_converge, #compile_and_converge_action, #converge_by, #converge_if_changed, #cookbook_name, #description, #events, include_resource_dsl?, include_resource_dsl_module, #initialize, #introduced, #load_after_resource, #node, #process_resource_requirements, provides, provides?, #requirements, #resource_collection, #resource_updated?, #run_action, #set_updated_status, supports?, use, use_inline_resources, #validate_required_properties!, #whyrun_mode?, #whyrun_supported?

Methods included from Mixin::Provides

#provided_as, #provides, #provides?

Methods included from Mixin::DescendantsTracker

#descendants, descendants, direct_descendants, #direct_descendants, find_descendants_by_name, #find_descendants_by_name, #inherited, store_inherited

Methods included from Mixin::LazyModuleInclude

#descendants, #include, #included

Methods included from Mixin::ShellOut

apply_default_env, maybe_add_timeout, #shell_out, #shell_out!

Methods included from Mixin::PowershellOut

#powershell_out, #powershell_out!

Methods included from Mixin::WindowsArchitectureHelper

#assert_valid_windows_architecture!, #disable_wow64_file_redirection, #forced_32bit_override_required?, #is_i386_process_on_x86_64_windows?, #node_supports_windows_architecture?, #node_windows_architecture, #restore_wow64_file_redirection, #valid_windows_architecture?, #with_os_architecture, #wow64_architecture_override_required?, #wow64_directory

Methods included from Mixin::PowershellExec

#powershell_exec, #powershell_exec!

Methods included from DSL::Powershell

#ps_credential

Methods included from DSL::RegistryHelper

#registry_data_exists?, #registry_get_subkeys, #registry_get_values, #registry_has_subkeys?, #registry_key_exists?, #registry_value_exists?

Methods included from DSL::ChefVault

#chef_vault, #chef_vault_item, #chef_vault_item_for_environment

Methods included from DSL::DataQuery

#data_bag, #data_bag_item, #search, #tagged?

Methods included from EncryptedDataBagItem::CheckEncrypted

#encrypted?

Methods included from DSL::PlatformIntrospection

#older_than_win_2012_or_8?, #platform?, #platform_family?, #value_for_platform, #value_for_platform_family

Methods included from DSL::Recipe

#exec, #have_resource_class_for?, #resource_class_for

Methods included from DSL::Definitions

add_definition, #evaluate_resource_definition, #has_resource_definition?

Methods included from DSL::Resources

add_resource_dsl, remove_resource_dsl

Methods included from DSL::Cheffish

load_cheffish

Methods included from DSL::RebootPending

#reboot_pending?

Methods included from DSL::IncludeRecipe

#include_recipe, #load_recipe

Methods included from Mixin::NotifyingBlock

#notifying_block, #subcontext_block

Methods included from DSL::DeclareResource

#build_resource, #declare_resource, #delete_resource, #delete_resource!, #edit_resource, #edit_resource!, #find_resource, #find_resource!, #resources, #with_run_context

Constructor Details

This class inherits a constructor from Chef::Provider

Instance Method Details

#add_remotesObject



157
158
159
160
161
162
163
164
165
166
# File 'lib/chef/provider/git.rb', line 157

def add_remotes
  if new_resource.additional_remotes.length > 0
    new_resource.additional_remotes.each_pair do |remote_name, remote_url|
      converge_by("add remote #{remote_name} from #{remote_url}") do
        logger.info "#{new_resource} adding git remote #{remote_name} = #{remote_url}"
        setup_remote_tracking_branches(remote_name, remote_url)
      end
    end
  end
end

#checkoutObject



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/chef/provider/git.rb', line 184

def checkout
  converge_by("checkout ref #{target_revision} branch #{new_resource.revision}") do
    # checkout into a local branch rather than a detached HEAD
    if new_resource.checkout_branch
      # check out to a local branch
      git("branch", "-f", new_resource.checkout_branch, target_revision, cwd: cwd)
      git("checkout", new_resource.checkout_branch, cwd: cwd)
      logger.info "#{new_resource} checked out branch: #{new_resource.revision} onto: #{new_resource.checkout_branch} reference: #{target_revision}"
    elsif sha_hash?(new_resource.revision) || !is_branch?
      # detached head
      git("checkout", target_revision, cwd: cwd)
      logger.info "#{new_resource} checked out reference: #{target_revision}"
    else
      # need a branch with a tracking branch
      git("branch", "-f", new_resource.revision, target_revision, cwd: cwd)
      git("checkout", new_resource.revision, cwd: cwd)
      git("branch", "-u", "#{new_resource.remote}/#{new_resource.revision}", cwd: cwd)
      logger.info "#{new_resource} checked out branch: #{new_resource.revision} reference: #{target_revision}"
    end
  end
end

#cloneObject



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/chef/provider/git.rb', line 168

def clone
  converge_by("clone from #{repo_url} into #{cwd}") do
    remote = new_resource.remote

    clone_cmd = ["clone"]
    clone_cmd << "-o #{remote}" unless remote == "origin"
    clone_cmd << "--depth #{new_resource.depth}" if new_resource.depth
    clone_cmd << "--no-single-branch" if new_resource.depth && git_has_single_branch_option?
    clone_cmd << "\"#{new_resource.repository}\""
    clone_cmd << "\"#{cwd}\""

    logger.info "#{new_resource} cloning repo #{repo_url} to #{cwd}"
    git clone_cmd
  end
end

#current_revision_matches_target_revision?Boolean

Returns:

  • (Boolean)


270
271
272
# File 'lib/chef/provider/git.rb', line 270

def current_revision_matches_target_revision?
  (!current_resource.revision.nil?) && (target_revision.strip.to_i(16) == current_resource.revision.strip.to_i(16))
end

#define_resource_requirementsObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/chef/provider/git.rb', line 43

def define_resource_requirements
  unless new_resource.user.nil?
    requirements.assert(:all_actions) do |a|
      a.assertion do
        begin
          get_homedir(new_resource.user)
        rescue ArgumentError
          false
        end
      end
      a.whyrun("User #{new_resource.user} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
      a.failure_message(Chef::Exceptions::User, "#{new_resource.user} required by resource #{new_resource.name} does not exist")
    end
  end

  # Parent directory of the target must exist.
  requirements.assert(:checkout, :sync) do |a|
    dirname = ::File.dirname(cwd)
    a.assertion { ::File.directory?(dirname) }
    a.whyrun("Directory #{dirname} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
    a.failure_message(Chef::Exceptions::MissingParentDirectory,
                      "Cannot clone #{new_resource} to #{cwd}, the enclosing directory #{dirname} does not exist")
  end

  requirements.assert(:all_actions) do |a|
    a.assertion { !(new_resource.revision =~ %r{^origin/}) }
    a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
      "Deploying remote branches is not supported. " +
      "Specify the remote branch as a local branch for " +
      "the git repository you're deploying from " +
      "(ie: '#{new_resource.revision.gsub("origin/", "")}' rather than '#{new_resource.revision}')."
  end

  requirements.assert(:all_actions) do |a|
    # this can't be recovered from in why-run mode, because nothing that
    # we do in the course of a run is likely to create a valid target_revision
    # if we can't resolve it up front.
    a.assertion { !target_revision.nil? }
    a.failure_message Chef::Exceptions::UnresolvableGitReference,
      "Unable to parse SHA reference for '#{new_resource.revision}' in repository '#{new_resource.repository}'. " +
      "Verify your (case-sensitive) repository URL and revision.\n" +
      "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
  end
end

#enable_submodulesObject



206
207
208
209
210
211
212
213
214
215
216
# File 'lib/chef/provider/git.rb', line 206

def enable_submodules
  if new_resource.enable_submodules
    converge_by("enable git submodules for #{new_resource}") do
      logger.info "#{new_resource} synchronizing git submodules"
      git("submodule", "sync", cwd: cwd)
      logger.info "#{new_resource} enabling git submodules"
      # the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
      git("submodule", "update", "--init", "--recursive", cwd: cwd)
    end
  end
end

#existing_git_clone?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/chef/provider/git.rb', line 140

def existing_git_clone?
  ::File.exist?(::File.join(cwd, ".git"))
end

#fetch_updatesObject



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/chef/provider/git.rb', line 218

def fetch_updates
  setup_remote_tracking_branches(new_resource.remote, new_resource.repository)
  converge_by("fetch updates for #{new_resource.remote}") do
    # since we're in a local branch already, just reset to specified revision rather than merge
    logger.trace "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
    git("fetch", "--prune", new_resource.remote, cwd: cwd)
    git("fetch", new_resource.remote, "--tags", cwd: cwd)
    if new_resource.checkout_branch
      # check out to a local branch
      git("branch", "-f", new_resource.checkout_branch, target_revision, cwd: cwd)
      git("checkout", new_resource.checkout_branch, cwd: cwd)
    elsif sha_hash?(new_resource.revision) || is_tag?
      # detached head
      git("reset", "--hard", target_revision, cwd: cwd)
    else
      # need a branch with a tracking branch
      git("branch", "-f", new_resource.revision, target_revision, cwd: cwd)
      git("checkout", new_resource.revision, cwd: cwd)
      git("branch", "-u", "#{new_resource.remote}/#{new_resource.revision}", cwd: cwd)
    end
  end
end

#find_current_revisionObject



148
149
150
151
152
153
154
155
# File 'lib/chef/provider/git.rb', line 148

def find_current_revision
  logger.trace("#{new_resource} finding current git revision")
  if ::File.exist?(::File.join(cwd, ".git"))
    # 128 is returned when we're not in a git repo. this is fine
    result = git("rev-parse", "HEAD", cwd: cwd, returns: [0, 128]).stdout.strip
  end
  sha_hash?(result) ? result : nil
end

#find_revision(refs, revision, suffix = "") ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/chef/provider/git.rb', line 311

def find_revision(refs, revision, suffix = "")
  found = refs_search(refs, rev_match_pattern("refs/tags/", revision) + suffix)
  if !found.empty?
    @is_tag = true
    found
  else
    found = refs_search(refs, rev_match_pattern("refs/heads/", revision) + suffix)
    if !found.empty?
      @is_branch = true
      found
    else
      refs_search(refs, revision + suffix)
    end
  end
end

#git_gem_versionObject Also known as: git_minor_version



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/chef/provider/git.rb', line 126

def git_gem_version
  return @git_gem_version if defined?(@git_gem_version)

  output = git("--version").stdout
  match = GIT_VERSION_PATTERN.match(output)
  if match
    @git_gem_version = Gem::Version.new(match[1])
  else
    logger.warn "Unable to parse git version from '#{output}'"
    @git_gem_version = nil
  end
  @git_gem_version
end

#git_has_single_branch_option?Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/chef/provider/git.rb', line 122

def git_has_single_branch_option?
  @git_has_single_branch_option ||= !git_gem_version.nil? && git_gem_version >= Gem::Version.new("1.7.10")
end

#git_ls_remote(rev_pattern) ⇒ Object



343
344
345
# File 'lib/chef/provider/git.rb', line 343

def git_ls_remote(rev_pattern)
  git("ls-remote", "\"#{new_resource.repository}\"", "\"#{rev_pattern}\"").stdout
end

#load_current_resourceObject



35
36
37
38
39
40
41
# File 'lib/chef/provider/git.rb', line 35

def load_current_resource
  @resolved_reference = nil
  @current_resource = Chef::Resource::Git.new(new_resource.name)
  if current_revision = find_current_revision
    current_resource.revision current_revision
  end
end

#multiple_remotes?(check_remote_command_result) ⇒ Boolean

Returns:

  • (Boolean)


262
263
264
# File 'lib/chef/provider/git.rb', line 262

def multiple_remotes?(check_remote_command_result)
  check_remote_command_result.exitstatus == 2
end

#refs_search(refs, pattern) ⇒ Object



347
348
349
# File 'lib/chef/provider/git.rb', line 347

def refs_search(refs, pattern)
  refs.find_all { |m| m[1] == pattern }
end

#remote_matches?(remote_url, check_remote_command_result) ⇒ Boolean

Returns:

  • (Boolean)


266
267
268
# File 'lib/chef/provider/git.rb', line 266

def remote_matches?(remote_url, check_remote_command_result)
  check_remote_command_result.stdout.strip.eql?(remote_url)
end

#remote_resolve_referenceObject



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/chef/provider/git.rb', line 287

def remote_resolve_reference
  logger.trace("#{new_resource} resolving remote reference")
  # The sha pointed to by an annotated tag is identified by the
  # '^{}' suffix appended to the tag. In order to resolve
  # annotated tags, we have to search for "revision*" and
  # post-process. Special handling for 'HEAD' to ignore a tag
  # named 'HEAD'.
  @resolved_reference = git_ls_remote(rev_search_pattern)
  refs = @resolved_reference.split("\n").map { |line| line.split("\t") }
  # First try for ^{} indicating the commit pointed to by an
  # annotated tag.
  # It is possible for a user to create a tag named 'HEAD'.
  # Using such a degenerate annotated tag would be very
  # confusing. We avoid the issue by disallowing the use of
  # annotated tags named 'HEAD'.
  if rev_search_pattern != "HEAD"
    found = find_revision(refs, new_resource.revision, "^{}")
  else
    found = refs_search(refs, "HEAD")
  end
  found = find_revision(refs, new_resource.revision) if found.empty?
  found.size == 1 ? found.first[0] : nil
end

#rev_match_pattern(prefix, revision) ⇒ Object



327
328
329
330
331
332
333
# File 'lib/chef/provider/git.rb', line 327

def rev_match_pattern(prefix, revision)
  if revision.start_with?(prefix)
    revision
  else
    prefix + revision
  end
end

#rev_search_patternObject



335
336
337
338
339
340
341
# File 'lib/chef/provider/git.rb', line 335

def rev_search_pattern
  if ["", "HEAD"].include? new_resource.revision
    "HEAD"
  else
    new_resource.revision + "*"
  end
end

#setup_remote_tracking_branches(remote_name, remote_url) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/chef/provider/git.rb', line 241

def setup_remote_tracking_branches(remote_name, remote_url)
  converge_by("set up remote tracking branches for #{remote_url} at #{remote_name}") do
    logger.trace "#{new_resource} configuring remote tracking branches for repository #{remote_url} " + "at remote #{remote_name}"
    check_remote_command = ["config", "--get", "remote.#{remote_name}.url"]
    remote_status = git(check_remote_command, cwd: cwd, returns: [0, 1, 2])
    case remote_status.exitstatus
    when 0, 2
      # * Status 0 means that we already have a remote with this name, so we should update the url
      #   if it doesn't match the url we want.
      # * Status 2 means that we have multiple urls assigned to the same remote (not a good idea)
      #   which we can fix by replacing them all with our target url (hence the --replace-all option)

      if multiple_remotes?(remote_status) || !remote_matches?(remote_url, remote_status)
        git("config", "--replace-all", "remote.#{remote_name}.url", %{"#{remote_url}"}, cwd: cwd)
      end
    when 1
      git("remote", "add", remote_name, remote_url, cwd: cwd)
    end
  end
end

#target_dir_non_existent_or_empty?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'lib/chef/provider/git.rb', line 144

def target_dir_non_existent_or_empty?
  !::File.exist?(cwd) || Dir.entries(cwd).sort == [".", ".."]
end

#target_revisionObject Also known as: revision_slug



274
275
276
277
278
279
280
281
282
283
# File 'lib/chef/provider/git.rb', line 274

def target_revision
  @target_revision ||=
    begin
      if sha_hash?(new_resource.revision)
        @target_revision = new_resource.revision
      else
        @target_revision = remote_resolve_reference
      end
    end
end