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