Class: Chef::Provider::Git

Inherits:
Chef::Provider show all
Includes:
Mixin::ShellOut
Defined in:
lib/chef/provider/git.rb

Constant Summary

Constants included from Mixin::ShellOut

Mixin::ShellOut::DEPRECATED_OPTIONS

Instance Attribute Summary

Attributes inherited from Chef::Provider

#action, #current_resource, #new_resource, #run_context

Instance Method Summary collapse

Methods included from Mixin::ShellOut

#run_command_compatible_options, #shell_out, #shell_out!

Methods inherited from Chef::Provider

#action_nothing, build_from_file, #cleanup_after_converge, #converge, #cookbook_name, #events, #initialize, #node, #process_resource_requirements, #requirements, #resource_collection, #run_action, #whyrun_mode?

Methods included from Mixin::ConvertToClassName

#convert_to_class_name, #convert_to_snake_case, #filename_to_qualified_string, #snake_case_basename

Methods included from Mixin::EnforceOwnershipAndPermissions

#access_controls, #enforce_ownership_and_permissions

Methods included from Mixin::RecipeDefinitionDSLCore

#method_missing

Methods included from Mixin::Language

#data_bag, #data_bag_item, #platform?, #platform_family?, #search, #value_for_platform, #value_for_platform_family

Constructor Details

This class inherits a constructor from Chef::Provider

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Chef::Mixin::RecipeDefinitionDSLCore

Instance Method Details

#action_checkoutObject



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

def action_checkout
  if target_dir_non_existent_or_empty?
    clone
    checkout
    enable_submodules
    add_remotes
  else
    Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} 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 #{@new_resource.destination}.git after checkout") do 
    FileUtils.rm_rf(::File.join(@new_resource.destination,".git"))
  end
end

#action_syncObject



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

def action_sync
  if existing_git_clone?
    current_rev = find_current_revision
    Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{target_revision}"
    unless current_revision_matches_target_revision?
      fetch_updates
      enable_submodules
      Chef::Log.info "#{@new_resource} updated to revision #{target_revision}"
    end
    add_remotes
  else
    action_checkout
  end
end

#add_remotesObject



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

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
        Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}"
        command = "git remote add #{remote_name} #{remote_url}"
        if shell_out(command, run_options(:cwd => @new_resource.destination, :log_level => :info)).exitstatus != 0
          @new_resource.updated_by_last_action(true)
        end
      end
    end
  end
end

#checkoutObject



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

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
    shell_out!("git checkout -b deploy #{sha_ref}", run_options(:cwd => @new_resource.destination))
    Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} reference: #{sha_ref}"
  end
end

#cloneObject



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

def clone 
  converge_by("clone from #{@new_resource.repository} into #{@new_resource.destination}") do
    remote = @new_resource.remote

    args = []
    args << "-o #{remote}" unless remote == 'origin'
    args << "--depth #{@new_resource.depth}" if @new_resource.depth

    Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"

    clone_cmd = "git clone #{args.join(' ')} #{@new_resource.repository} #{Shellwords.escape @new_resource.destination}"
    shell_out!(clone_cmd, run_options(:log_level => :info))
  end
end

#current_revision_matches_target_revision?Boolean

Returns:

  • (Boolean)


203
204
205
# File 'lib/chef/provider/git.rb', line 203

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



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

def define_resource_requirements
  # Parent directory of the target must exist. 
  requirements.assert(:checkout, :sync) do |a|
    dirname = ::File.dirname(@new_resource.destination)
    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 #{@new_resource.destination}, the enclosing directory #{dirname} does not exist")
  end


  requirements.assert(:all_actions) do |a|
    a.assertion { !(@new_resource.revision =~ /^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` output: #{@resolved_reference}"
  end
end

#enable_submodulesObject



165
166
167
168
169
170
171
172
173
174
# File 'lib/chef/provider/git.rb', line 165

def enable_submodules
  if @new_resource.enable_submodules
    converge_by("enable git submodules for #{@new_resource}") do 
      Chef::Log.info "#{@new_resource} enabling git submodules"
      # the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
      command = "git submodule update --init --recursive"
      shell_out!(command, run_options(:cwd => @new_resource.destination, :log_level => :info))
    end
  end
end

#existing_git_clone?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/chef/provider/git.rb', line 110

def existing_git_clone?
  ::File.exist?(::File.join(@new_resource.destination, ".git"))
end

#fetch_updatesObject



176
177
178
179
180
181
182
183
184
# File 'lib/chef/provider/git.rb', line 176

def fetch_updates
  setup_remote_tracking_branches if @new_resource.remote != 'origin'
  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
    fetch_command = "git fetch #{@new_resource.remote} && git fetch #{@new_resource.remote} --tags && git reset --hard #{target_revision}"
    Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
    shell_out!(fetch_command, run_options(:cwd => @new_resource.destination))
  end
end

#find_current_revisionObject



118
119
120
121
122
123
124
125
# File 'lib/chef/provider/git.rb', line 118

def find_current_revision
  Chef::Log.debug("#{@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 = shell_out!('git rev-parse HEAD', :cwd => cwd, :returns => [0,128]).stdout.strip
  end
  sha_hash?(result) ? result : nil
end

#load_current_resourceObject



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

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

#remote_resolve_referenceObject



219
220
221
222
223
224
225
226
227
228
# File 'lib/chef/provider/git.rb', line 219

def remote_resolve_reference
  Chef::Log.debug("#{@new_resource} resolving remote reference")
  command = git('ls-remote', @new_resource.repository, @new_resource.revision)
  @resolved_reference = shell_out!(command, run_options).stdout
  if  @resolved_reference =~ /^([0-9a-f]{40})\s+(\S+)/
    $1
  else
    nil
  end
end

#setup_remote_tracking_branchesObject

Use git-config to setup a remote tracking branches. Could use git-remote but it complains when a remote of the same name already exists, git-config will just silenty overwrite the setting every time. This could cause wierd-ness in the remote cache if the url changes between calls, but as long as the repositories are all based from each other it should still work fine.



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

def setup_remote_tracking_branches
  command = []
  converge_by("set up remote tracking branches for #{@new_resource.repository} at #{@new_resource.remote}") do
    Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{@new_resource.repository} "+
                    "at remote #{@new_resource.remote}"
    command << "git config remote.#{@new_resource.remote}.url #{@new_resource.repository}"
    command << "git config remote.#{@new_resource.remote}.fetch +refs/heads/*:refs/remotes/#{@new_resource.remote}/*"
    shell_out!(command.join(" && "), run_options(:cwd => @new_resource.destination))
  end
end

#target_dir_non_existent_or_empty?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/chef/provider/git.rb', line 114

def target_dir_non_existent_or_empty?
  !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
end

#target_revisionObject Also known as: revision_slug



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

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

#whyrun_supported?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/chef/provider/git.rb', line 32

def whyrun_supported?
  true
end