lib/omnibus/fetchers/git_fetcher.rb in omnibus-3.2.2 vs lib/omnibus/fetchers/git_fetcher.rb in omnibus-4.0.0.beta.1

- old
+ new

@@ -13,204 +13,169 @@ # See the License for the specific language governing permissions and # limitations under the License. # module Omnibus - # Fetcher implementation for projects in git. class GitFetcher < Fetcher - attr_reader :source - attr_reader :project_dir - attr_reader :version - - def initialize(software) - @name = software.name - @source = software.source - @version = software.version - @project_dir = software.project_dir - super + # + # A fetch is required if the git repository is not cloned or if the local + # revision does not match the desired revision. + # + # @return [true, false] + # + def fetch_required? + !(cloned? && same_revision?) end - def description - <<-EOH.gsub(/^ {8}/, '').strip - repo URI: #{@source[:git]} - local location: #{@project_dir} - EOH - end - + # + # The version identifier for this git location. This is computed using the + # current revision on disk. + # + # @return [String] + # def version_guid - "git:#{current_revision}".chomp - rescue + "git:#{current_revision}" end + # + # Clean the project directory by removing the contents from disk. + # + # @return [true, false] + # true if the project directory was removed, false otherwise + # def clean - if existing_git_clone? - log.info(log_key) { 'Cleaning existing build' } - shellout!('git clean -fdx', cwd: project_dir) + if cloned? + log.info(log_key) { 'Cleaning existing clone' } + git('clean -fdx') + true + else + false end - rescue Exception => e - ErrorReporter.new(e, self).explain("Failed to clean git repository '#{@source[:git]}'") - raise end - def fetch_required? - !existing_git_clone? || !current_rev_matches_target_rev? - end - + # + # Fetch (clone) or update (fetch) the remote git repository. + # + # @return [void] + # def fetch - retries ||= 0 - if existing_git_clone? - fetch_updates unless current_rev_matches_target_rev? + log.info(log_key) { "Fetching from `#{source_url}'" } + + create_required_directories + + if cloned? + git_fetch unless same_revision? else - clone - checkout + git_clone + git_checkout end - rescue Exception => e - if retries >= 3 - ErrorReporter.new(e, self).explain("Failed to fetch git repository '#{@source[:git]}'") - raise - else - # Deal with github failing all the time :( - time_to_sleep = 5 * (2**retries) - retries += 1 - log.warn(log_key) do - "git clone/fetch failed for #{@source} #{retries} time(s). " \ - "Retrying in #{time_to_sleep}s..." - end - sleep(time_to_sleep) - retry - end end - # Return the target sha to be used during build caching - # This overrides the cases where software.version is similar to - # master, 11-stable etc.. + # + # The version for this item in the cache. The is the parsed revision of the + # item on disk. + # + # @return [String] + # def version_for_cache - target_revision + "revision:#{current_revision}" end private - def clone - log.info(log_key) { 'Cloning the source from git' } - shellout!("git clone #{@source[:git]} #{project_dir}") + # + # The URL where the git source should be downloaded from. + # + # @return [String] + # + def source_url + source[:git] end - def checkout - sha_ref = target_revision - shellout!("git checkout #{sha_ref}", cwd: project_dir) + # + # Determine if the clone exists. + # + # @return [true, false] + # + def cloned? + File.exist?("#{project_dir}/.git") end - def fetch_updates - log.info(log_key) do - "Fetching updates and resetting to revision '#{target_revision}'" - end - - fetch_cmd = "git fetch origin && " \ - "git fetch origin --tags && " \ - "git reset --hard #{target_revision}" - shellout!(fetch_cmd, cwd: project_dir) + # + # Clone the +source_url+ into the +project_dir+. + # + # @return [void] + # + def git_clone + git("clone #{source_url} .") end - def existing_git_clone? - File.exist?("#{project_dir}/.git") + # + # Checkout the +target_revision+. + # + # @return [void] + # + def git_checkout + git("fetch --all") + git("checkout #{target_revision}") end - def current_rev_matches_target_rev? - current_revision && current_revision.strip.to_i(16) == target_revision.strip.to_i(16) + # + # Fetch the remote tags and refs, and reset to +target_revision+. + # + # @return [void] + # + def git_fetch + git("fetch --all") + git("reset --hard #{target_revision}") end + # + # The current revision for the cloned checkout. + # + # @return [String] + # def current_revision - return @current_rev if @current_rev - - cmd = shellout!('git rev-parse HEAD', cwd: project_dir) - stdout = cmd.stdout - - @current_rev = sha_hash?(stdout) ? stdout : nil - @current_rev + @current_revision ||= git('rev-parse HEAD').stdout.strip + rescue CommandFailed + nil end + # + # The target revision from the user. + # + # @return [String] + # def target_revision - @target_rev ||= if sha_hash?(version) - version - else - revision_from_remote_reference(version) - end - end - - def sha_hash?(rev) - rev =~ /^[0-9a-f]{40}$/ - end - - # Return the SHA corresponding to ref. If ref is an annotated tag, - # return the SHA that was tagged not the SHA of the tag itself. - def revision_from_remote_reference(ref) - retries ||= 0 - # execute `git ls-remote` the trailing '*' does globbing. This - # allows us to return the SHA of the tagged commit for annotated - # tags. We take care to only return exact matches in - # process_remote_list. - cmd = shellout!("git ls-remote origin #{ref}*", cwd: project_dir) - commit_ref = process_remote_list(cmd.stdout, ref) - - unless commit_ref - raise UnresolvableGitReference.new("Could not resolve `#{ref}' to a SHA.") - end - commit_ref - rescue UnresolvableGitReference => e # skip retries - ErrorReporter.new(e, self).explain(<<-E) -Command `#{cmd}' did not find a commit for reference `#{ref}'. -The tag or branch you're looking for doesn't exist on the remote repo. -If your project uses version tags like v1.2.3, include the 'v' in your -software's version. -E - raise - rescue Exception => e - if retries >= 3 - ErrorReporter.new(e, self).explain("Failed to find any commits for the ref '#{ref}'") - raise + @target_revision ||= git("rev-parse #{version}").stdout.strip + rescue CommandFailed => e + if e.message.include?('ambiguous argument') + @target_revision = git("rev-parse origin/#{version}").stdout.strip + @target_revision else - # Deal with github failing all the time :( - time_to_sleep = 5 * (2**retries) - retries += 1 - log.warn(log_key) do - "git ls-remote failed for #{@source} #{retries} time(s). " \ - "Retrying in #{time_to_sleep}s..." - end - sleep(time_to_sleep) - retry + log.warn { 'Could not determine target revision!' } + nil end end - def process_remote_list(stdout, ref) - # Dereference annotated tags. - # - # Output will look like this: - # - # a2ed66c01f42514bcab77fd628149eccb4ecee28 refs/tags/rel-0.11.0 - # f915286abdbc1907878376cce9222ac0b08b12b8 refs/tags/rel-0.11.0^{} - # - # The SHA with ^{} is the commit pointed to by an annotated - # tag. If ref isn't an annotated tag, there will not be a line - # with trailing ^{}. - # - # We'll return the SHA corresponding to the ^{} which is the - # commit pointed to by an annotated tag. If no such commit - # exists (not an annotated tag) then we return the SHA of the - # ref. If nothing matches, return "". - lines = stdout.split("\n") - matches = lines.map { |line| line.split("\t") } - # first try for ^{} indicating the commit pointed to by an - # annotated tag - tagged_commit = matches.find { |m| m[1].end_with?("#{ref}^{}") } - if tagged_commit - tagged_commit[0] - else - found = matches.find { |m| m[1].end_with?("#{ref}") } - if found - found[0] - else - nil - end - end + # + # Determine if the given revision matches the current revision. + # + # @return [true, false] + # + def same_revision? + current_revision == target_revision + end + + # + # Execute the given git command, inside the +project_dir+. + # + # @see Util#shellout! + # + # @return [Mixlib::ShellOut] + # the shellout object + # + def git(command) + shellout!("git #{command}", cwd: project_dir) end end end