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, #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, #node, #process_resource_requirements, provides, provides?, #requirements, #resource_collection, #resource_updated?, #run_action, #set_updated_status, supports?, use_inline_resources, #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

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

#action_checkoutObject



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/chef/provider/git.rb', line 74

def action_checkout
  if target_dir_non_existent_or_empty?
    clone
    if new_resource.enable_checkout
      checkout
    end
    enable_submodules
    add_remotes
  else
    logger.trace "#{new_resource} checkout destination #{cwd} already exists or is a non-empty directory"
  end
end

#action_exportObject



87
88
89
90
91
92
# File 'lib/chef/provider/git.rb', line 87

def action_export
  action_checkout
  converge_by("complete the export by removing #{cwd}.git after checkout") do
    FileUtils.rm_rf(::File.join(cwd, ".git"))
  end
end

#action_syncObject



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/chef/provider/git.rb', line 94

def action_sync
  if existing_git_clone?
    logger.trace "#{new_resource} current revision: #{current_resource.revision} target revision: #{target_revision}"
    unless current_revision_matches_target_revision?
      fetch_updates
      enable_submodules
      logger.info "#{new_resource} updated to revision #{target_revision}"
    end
    add_remotes
  else
    action_checkout
  end
end

#add_remotesObject



143
144
145
146
147
148
149
150
151
152
# File 'lib/chef/provider/git.rb', line 143

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



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

def checkout
  sha_ref = target_revision

  converge_by("checkout ref #{sha_ref} branch #{new_resource.revision}") do
    # checkout into a local branch rather than a detached HEAD
    git("branch", "-f", new_resource.checkout_branch, sha_ref, 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: #{sha_ref}"
  end
end

#cloneObject



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

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)


233
234
235
# File 'lib/chef/provider/git.rb', line 233

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
# File 'lib/chef/provider/git.rb', line 43

def define_resource_requirements
  # 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



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/chef/provider/git.rb', line 181

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)


126
127
128
# File 'lib/chef/provider/git.rb', line 126

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

#fetch_updatesObject



193
194
195
196
197
198
199
200
201
202
# File 'lib/chef/provider/git.rb', line 193

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)
    git("reset", "--hard", target_revision, cwd: cwd)
  end
end

#find_current_revisionObject



134
135
136
137
138
139
140
141
# File 'lib/chef/provider/git.rb', line 134

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



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

def find_revision(refs, revision, suffix = "")
  found = refs_search(refs, rev_match_pattern("refs/tags/", revision) + suffix)
  found = refs_search(refs, rev_match_pattern("refs/heads/", revision) + suffix) if found.empty?
  found = refs_search(refs, revision + suffix) if found.empty?
  found
end

#git_gem_versionObject Also known as: git_minor_version



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/chef/provider/git.rb', line 112

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)


108
109
110
# File 'lib/chef/provider/git.rb', line 108

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



297
298
299
# File 'lib/chef/provider/git.rb', line 297

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)


225
226
227
# File 'lib/chef/provider/git.rb', line 225

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

#refs_search(refs, pattern) ⇒ Object



301
302
303
# File 'lib/chef/provider/git.rb', line 301

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

#remote_matches?(remote_url, check_remote_command_result) ⇒ Boolean

Returns:

  • (Boolean)


229
230
231
# File 'lib/chef/provider/git.rb', line 229

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

#remote_resolve_referenceObject



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

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



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

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

#rev_search_patternObject



289
290
291
292
293
294
295
# File 'lib/chef/provider/git.rb', line 289

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



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/chef/provider/git.rb', line 204

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)


130
131
132
# File 'lib/chef/provider/git.rb', line 130

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

#target_revisionObject Also known as: revision_slug



237
238
239
240
241
242
243
244
245
246
# File 'lib/chef/provider/git.rb', line 237

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