class Ro::Git

Attributes

branch[RW]
patching[RW]
root[RW]

Public Class Methods

new(root, options = {}) click to toggle source
# File lib/ro/git.rb, line 7
def initialize(root, options = {})
  options = Map.for(options)

  @root = root
  @branch = options[:branch] || 'master'
end

Public Instance Methods

patch(*args, &block) click to toggle source

patch takes a block, allows abitrary edits (additions, modifications, deletions) to be performed by it, and then computes a single, atomic patch that is applied to the repo and pushed. the patch is returned. if the patch was not applied then patch.applied==false and it’s up to client code to decide how to proceed, perhaps retrying or saving the patchfile for later manual application

# File lib/ro/git.rb, line 21
def patch(*args, &block)
  options = Map.options_for!(args)

  user = options[:user] || ENV['USER'] || 'ro'
  msg = options[:message] || "#{ user } edits on #{ File.basename(@root).inspect }"
  add = options.has_key?(:add) ? options[:add] : true

  patch = nil

  Thread.exclusive do
    @root.lock do
      Dir.chdir(@root) do
        # ensure .git-ness
        #
          status, stdout, stderr = spawn("git rev-parse --git-dir", :raise => true, :capture => true)

          git_root = stdout.to_s.strip

          dot_git = File.expand_path(git_root)

          unless test(?d, dot_git)
            raise Error.new("missing .git directory #{ dot_git }")
          end

        # calculate a tmp branch name
        #
          time = Coerce.time(options[:time] || Time.now).utc.iso8601(2).gsub(/[^\w]/, '')
          branch = "#{ user }-#{ time }-#{ rand.to_s.gsub(/^0./, '') }"

        # allow block to edit, compute the patch, attempt to apply it
        #
          begin
          # get pristine
          #
            spawn("git checkout -f master", :raise => true)
            spawn("git fetch --all", :raise => true)
            spawn("git reset --hard origin/master", :raise => true)

          # pull recent changes
          #
            trying('to pull'){ spawn("git pull origin master") }

          # create a new temporary branch
          #
            spawn("git checkout -b #{ branch.inspect }", :raise => true)

          # the block can perform arbitrary edits
          #
            block.call

          # add all changes - additions, deletions, or modifications - unless :add => false was specified
          #
            if add
              spawn("git add . --all", :raise => true)
            end

          # commit if anything changed
          #
            changes_to_apply =
              spawn("git commit -m #{ msg.inspect }")

            if changes_to_apply
            # create the patch
            #
              status, stdout, stderr =
                spawn("git format-patch master --stdout", :raise => true, :capture => true)

              patch = Patch.new(:data => stdout, :name => branch)

              unless stdout.to_s.strip.empty?
              # apply the patch
              #
                spawn("git checkout master", :raise => true)

              #
                spawn("git rebase --abort")
                spawn("git am --abort")

                spawn("git am --abort")
                spawn("git rebase --abort")

              #
                status, stdout, stderr =
                  spawn("git am --signoff --3way --ignore-space-change --ignore-whitespace", :capture => true, :stdin => patch.data)

              #
                patch.applied = !!(status == 0)

              # commit the patch back to the repo
              #
                patch.committed =
                  begin
                    trying('to pull'){ spawn("git pull origin master") }
                    trying('to push'){ spawn("git push origin master") }
                    true
                  rescue Object
                    false
                  end
              end
            end
          ensure
          # get pristine
          #
            spawn("git checkout -f master", :raise => true)
            spawn("git fetch --all", :raise => true)
            spawn("git reset --hard origin/master", :raise => true)

            spawn("git am --abort")
            spawn("git rebase --abort")

          # get changes
          #
            trying('to pull'){ spawn("git pull") }

          # nuke the tmp branch
          #
            if patch and patch.applied and patch.committed
              spawn("git branch -D #{ branch.inspect }")
            end
          end
      end
    end
  end

  patch
end
save(directory, options = {}) click to toggle source
# File lib/ro/git.rb, line 204
    def save(directory, options = {})
      if directory.is_a?(Node)
        directory = directory.path
      end

      options = Map.for(options)

      dir = File.expand_path(directory.to_s)

      relative_path = Ro.relative_path(dir, :from => @root)

      exists = test(?d, dir)

      action = exists ? 'edited' : 'created'

      msg = options[:message] || "#{ ENV['USER'] } #{ action } #{ relative_path }"

      @root.lock do
        FileUtils.mkdir_p(dir) unless exists

        Dir.chdir(dir) do
        # .git
        #
          status, stdout, stderr = spawn("git rev-parse --git-dir", :raise => true, :capture => true)

          git_root = stdout.to_s.strip

          dot_git = File.expand_path(git_root)

          unless test(?d, dot_git)
            raise Error.new("missing .git directory #{ dot_git }")
          end

        # correct branch
        #
          spawn("git checkout #{ @branch.inspect }", :raise => true)

        # return if nothing to do...
        #
          if `git status --porcelain`.strip.empty?
            return true
          end

        # commit the work
        #
          trying "to commit" do

            committed = 
              spawn("git add --all . && git commit -m #{ msg.inspect } -- .")

=begin
            unless committed
              spawn "git reset --hard"
            end
=end

#require 'pry'
#binding.pry
=begin
            retried = false
            begin
              spawn "git add --all . && git commit -m #{ msg.inspect } -- ."
              committed = true
            rescue
              raise if retried
              spawn "git reset --hard", :raise => false
              retry
            end
=end
          end


          trying "to push" do
            pushed = nil

            unless spawn("git push origin master")
            # merge
            #
              unless spawn("git pull")
                spawn("git checkout --ours -- .")
                spawn("git add --all .")
                spawn("git commit -F #{ dot_git }/MERGE_MSG")
              else
                raise 'wtf!?'
              end

              pushed = spawn("git push origin master")
            else
              pushed = true
            end

            pushed
          end

=begin
  git push

    git pull

      # publish
      git checkout --ours -- .
      git add --all .
      git commit -F .git/MERGE_MSG
      git push
=end



        end
      end
    end
spawn(command, options = {}) click to toggle source
# File lib/ro/git.rb, line 351
def spawn(command, options = {})
  options = Map.for(options)

  status, stdout, stderr = systemu(command, :stdin => options[:stdin])

  Ro.log(:debug, "command: #{ command }")
  Ro.log(:debug, "status: #{ status }")
  Ro.log(:debug, "stdout:\n#{ stdout }")
  Ro.log(:debug, "stderr:\n#{ stderr }")

  if options[:raise] == true
    unless status == 0
      raise "command (#{ command }) failed with #{ status }"
    end
  end

  if options[:capture]
    [status, stdout, stderr]
  else
    status == 0
  end
end
trying(*args, &block) click to toggle source
# File lib/ro/git.rb, line 318
def trying(*args, &block)
  options = Map.options_for!(args)
  label = ['trying', *args].join(' - ')

  n = Integer(options[:n] || 3)
  timeout = options[:timeout]
  e = nil
  done = nil
  not_done = Object.new.freeze

  result =
    catch(:trying) do
      n.times do |i|
        done = block.call
        if done
          throw(:trying, done)
        else
          unless timeout == false
            sleep( (i + 1) * (timeout || (1 + rand)) )
          end
        end
      end

      not_done
    end

  if result == not_done
    raise(Error.new("#{ label } failed #{ n } times"))
  else
    done
  end
end