lib/between_meals/repo/git.rb (162 lines of code) (raw):

# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 # Copyright 2013-present Facebook # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Require openssl in order to make rugged work reliably require 'openssl' require 'rugged' require 'mixlib/shellout' require 'between_meals/changeset' require 'between_meals/repo/git/cmd' module BetweenMeals class Repo class Git < BetweenMeals::Repo def setup if File.exist?(File.expand_path(@repo_path)) begin @repo = Rugged::Repository.new(File.expand_path(@repo_path)) rescue StandardError @repo = nil end else @repo = nil end @bin = 'git' @cmd = BetweenMeals::Repo::Git::Cmd.new( :bin => @bin, :cwd => @repo_path, :logger => @logger, ) end # Allow people to get access to the underlying Rugged # object for their hooks def repo_object @repo end def exists? !@repo.nil? end def head_rev @repo.head.target.oid end def last_msg @repo.head.target.message end def last_msg=(msg) @repo.head.target.amend( { :message => msg, :update_ref => 'HEAD', }, ) end def last_author @repo.head.target.to_hash[:author] end def head_parents @repo.head.target.parents.map do |x| { :rev => x.tree.oid, :time => x.time } end end def checkout(url) @cmd.clone(url, @repo_path) @repo = Rugged::Repository.new(File.expand_path(@repo_path)) end # Return files changed between two revisions def changes(start_ref, end_ref) valid_ref?(start_ref) valid_ref?(end_ref) if end_ref stdout = @cmd.diff(start_ref, end_ref).stdout begin parse_status(stdout).compact rescue StandardError => e # We've seen some weird non-reproducible failures here @logger.error( 'Something went wrong. Please report this output.', ) @logger.error(e) stdout.lines.each do |line| @logger.error(line.strip) end exit(1) end end def update @cmd.pull.stdout end # Return all files def files @repo.index.map { |x| { :path => x[:path], :status => :created } } end def upstream?(rev, master = 'upstream/master') if @cmd.merge_base(rev, master).stdout.strip == rev return true end return false rescue StandardError return false end def status @cmd.status.stdout.strip end def name @cmd.config('user.name').stdout.strip end def email @cmd.config('user.email').stdout.strip end def valid_ref?(ref) unless @repo.exists?(ref) fail Changeset::ReferenceError end end private def parse_status(changes) # man git-diff-files # Possible status letters are: # # A: addition of a file # C: copy of a file into a new one # D: deletion of a file # M: modification of the contents or mode of a file # R: renaming of a file # T: change in the type of the file # U: file is unmerged (you must complete the merge before it can # be committed) # X: "unknown" change type (most probably a bug, please report it) # rubocop:disable MultilineBlockChain changes.lines.map do |line| parts = line.chomp.split("\t") case parts[0] when 'A' # A path { :status => :modified, :path => parts[1], } when /^C(?:\d*)/ # C<numbers> path1 path2 { :status => :modified, :path => parts[2], } when 'D' # D path { :status => :deleted, :path => parts[1], } when /^M(?:\d*)/ # M<numbers> path { :status => :modified, :path => parts[1], } when /^R(?:\d*)/ # R<numbers> path1 path2 [ { :status => :deleted, :path => parts[1], }, { :status => :modified, :path => parts[2], }, ] when 'T' # T path [ { :status => :deleted, :path => parts[1], }, { :status => :modified, :path => parts[1], }, ] else fail 'Failed to parse repo diff line.' end end.flatten.map do |x| { :status => x[:status], :path => x[:path].sub("#{@repo_path}/", ''), } end # rubocop:enable MultilineBlockChain end end end end