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::LazyModuleInclude

#descendants, #include, #included

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 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 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


162
163
164
165
166
167
168
169
170
171
# File 'lib/chef/provider/git.rb', line 162

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

#already_on_target_branch?Boolean

Returns:

  • (Boolean)

157
158
159
160
# File 'lib/chef/provider/git.rb', line 157

def already_on_target_branch?
  current_branch = git("rev-parse", "--abbrev-ref", "HEAD", cwd: cwd, returns: [0, 128]).stdout.strip
  current_branch == (new_resource.checkout_branch || new_resource.revision)
end

#checkoutObject


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/chef/provider/git.rb', line 189

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}"
    elsif already_on_target_branch?
      # we are already on the proper branch
      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)
      logger.info "#{new_resource} checked out branch: #{new_resource.revision} reference: #{target_revision}"
    end
  end
end

#cloneObject


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/chef/provider/git.rb', line 173

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)

278
279
280
# File 'lib/chef/provider/git.rb', line 278

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

        get_homedir(new_resource.user)
      rescue ArgumentError
        false

      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


214
215
216
217
218
219
220
221
222
223
224
# File 'lib/chef/provider/git.rb', line 214

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


226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/chef/provider/git.rb', line 226

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 sha_hash?(new_resource.revision) || is_tag? || already_on_target_branch?
      # detached head or if we are already on the proper branch
      git("reset", "--hard", target_revision, cwd: cwd)
    elsif 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)
    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


317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/chef/provider/git.rb', line 317

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


349
350
351
# File 'lib/chef/provider/git.rb', line 349

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)

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

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

#refs_search(refs, pattern) ⇒ Object


353
354
355
# File 'lib/chef/provider/git.rb', line 353

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

#remote_matches?(remote_url, check_remote_command_result) ⇒ Boolean

Returns:

  • (Boolean)

274
275
276
# File 'lib/chef/provider/git.rb', line 274

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

#remote_resolve_referenceObject


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/chef/provider/git.rb', line 293

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


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

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

#rev_search_patternObject


341
342
343
344
345
346
347
# File 'lib/chef/provider/git.rb', line 341

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


249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/chef/provider/git.rb', line 249

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


282
283
284
285
286
287
288
289
# File 'lib/chef/provider/git.rb', line 282

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