Module: ReleaseManager::Git::Utilities

Included in:
Changelog, ControlRepo, PuppetModule, Puppetfile, R10kDeployer, Sandbox
Defined in:
lib/release_manager/git/utilites.rb

Instance Method Summary collapse

Instance Method Details

#add_all(pathspec = []) ⇒ Object

Parameters:

  • pathspec (defaults to: [])

    Array path specs as strings in an array



262
263
264
265
266
# File 'lib/release_manager/git/utilites.rb', line 262

def add_all(pathspec = [])
  index = repo.index
  index.add_all
  index.write
end

#add_file(file) ⇒ Object

Parameters:

  • file (String)
    • the path to the file you want to add



269
270
271
272
273
274
275
# File 'lib/release_manager/git/utilites.rb', line 269

def add_file(file)
  logger.debug("Adding file #{file}")
  return add_all if file == '.'
  index = repo.index
  file.slice!(repo.workdir)
  index.add(:path => file, :oid => Rugged::Blob.from_workdir(repo, file), :mode => 0100644, valid: false)
end

#add_remote(url, remote_name = 'upstream', reset_url = false) ⇒ Rugged::Remote

Returns a rugged remote object.

Parameters:

  • url (String)
    • the url of the remote

  • remote_name (String) (defaults to: 'upstream')
    • the name of the remote

  • reset_url (Boolean) (defaults to: false)
    • set to true if you wish to reset the remote url

Returns:

  • (Rugged::Remote)

    a rugged remote object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/release_manager/git/utilites.rb', line 61

def add_remote(url, remote_name = 'upstream', reset_url = false )
  return false unless git_url?(url)
  url = url.gsub('"', '') # remove quotes if url contains quotes
  if remote_exists?(remote_name)
    # ensure the correct url is set
    # this sets a non persistant fetch url
    unless remote_url_matches?(remote_name, url)
      if reset_url
        logger.info("Resetting #{remote_name} remote to #{url} for #{path}")
        repo.remotes.set_url(remote_name,url)
        repo.remotes[remote_name]
      end
    end
  else
    logger.info("Adding #{remote_name} remote to #{url} for #{path}")
    repo.remotes.create(remote_name, url)
  end
end

#apply_diff(diff) ⇒ Object

Rugged::Diff

a rugged diff object

Not fully tested



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/release_manager/git/utilites.rb', line 298

def apply_diff(diff)
  diff.deltas.each do |d|
    case d.status
      when :deleted
        remove_file(d.new_file[:path])
        File.delete(File.join(path, path))
      when :added, :modified
        add_file(d.new_file[:path])
      when :renamed
        remove_file(d.old_file[:path])
        File.delete(File.join(path, path))
        add_file(d.new_file[:path])
      else
        logger.warn("File has a status of #{d.status}")
    end
  end
end

#apply_patch(file) ⇒ Object

Parameters:

  • file (String)
    • the path to the patch file you want to apply

Raises:



278
279
280
281
282
283
284
285
286
287
288
# File 'lib/release_manager/git/utilites.rb', line 278

def apply_patch(file)
  # TODO: change this to rugged implementation
  empty = File.read(file).length < 1
  logger.info("Applying patch #{file}")
  output = ''
  logger.debug("The patch file is empty for some reason") if empty
  Dir.chdir(path) do
    output = `#{git_command} apply #{file} 2>&1`
  end
  raise PatchError.new(output) unless $?.success?
end

#authorHash

Returns the author information used in a commit message.

Returns:

  • (Hash)

    the author information used in a commit message



257
258
259
# File 'lib/release_manager/git/utilites.rb', line 257

def author
  {:email=>author_email, :time=>Time.now, :name=>author_name}
end

#author_emailString

Returns - the author email found in the config.

Returns:

  • (String)
    • the author email found in the config



252
253
254
# File 'lib/release_manager/git/utilites.rb', line 252

def author_email
  repo.config.get('user.email') || Rugged::Config.global.get('user.email') || ENV['GIT_USER_EMAIL']
end

#author_nameString

Returns - the author name found in the config.

Returns:

  • (String)
    • the author name found in the config



247
248
249
# File 'lib/release_manager/git/utilites.rb', line 247

def author_name
  repo.config.get('user.name') || Rugged::Config.global.get('user.name') || ENV['GIT_USER_NAME']
end

#branch_exist?(name) ⇒ Boolean

Returns - true if the branch exist.

Parameters:

  • name (String)
    • the name of the branch

Returns:

  • (Boolean)
    • true if the branch exist



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/release_manager/git/utilites.rb', line 95

def branch_exist?(name)
  # ensure we have the latest branches
  remote_name, ref = name.split('/', 2)
  if name.include?('/')
    remote_name, ref = name.split('/', 2)
  else
    ref = name
  end
  # check to see if we just needed to fetch the upstreams
  fetch(remote_name) unless (repo.branches.exist?(name) || ref_exists?(name) || tag_exists?(ref))
  repo.branches.exist?(name) || ref_exists?(name) || tag_exists?(ref)
end

#changed_files(src_ref, dst_ref) ⇒ Array[String]

Returns the changed files in the commit or all the commits in the diff between src and dst.

Parameters:

  • src (Rubbed::Object)
    • the rugged object to compare from

  • dst (Rubbed::Object)
    • the rugged object to compare to

Returns:

  • (Array[String])

    the changed files in the commit or all the commits in the diff between src and dst



401
402
403
404
405
406
# File 'lib/release_manager/git/utilites.rb', line 401

def changed_files(src_ref, dst_ref)
  src = repo.lookup(find_ref(src_ref))
  src = src.kind_of?(Rugged::Tag::Annotation) ? src.target : src
  dst = repo.lookup(find_ref(dst_ref))
  dst.diff(src).deltas.map { |d| [d.old_file[:path], d.new_file[:path]] }.flatten.uniq
end

#checkout_branch(name, options = {}) ⇒ Rugged::Branch

Returns the rugged branch object

Parameters:

  • name (String)
    • the name of the branch to checkout

Returns:

  • (Rugged::Branch)

    returns the rugged branch object



204
205
206
207
208
209
210
211
212
213
214
# File 'lib/release_manager/git/utilites.rb', line 204

def checkout_branch(name, options = {})
  if current_branch?(name)
    logger.debug("Checking out branch: #{name} for #{path}")
    repo.checkout(name, options)
    logger.debug("Checked out branch: #{current_branch} for #{path}")
  else
    # already checked out
    logger.debug("Currently on branch #{name} for #{path}")
    repo.branches[name]
  end
end

#cherry_pick(commit) ⇒ Object



392
393
394
395
396
# File 'lib/release_manager/git/utilites.rb', line 392

def cherry_pick(commit)
  return unless commit
  repo.cherrypick(commit)
  logger.info("Cherry picking commit with id: #{commit}")
end

#cli_create_commit(message) ⇒ Object

TODO: change this to rugged implementation

Parameters:

  • message (String)
    • the message you want in the commit



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/release_manager/git/utilites.rb', line 361

def cli_create_commit(message)
  output = nil
  Dir.chdir(path) do
    output = `#{git_command} commit --message '#{message}' 2>&1`
  end
  if $?.success?
    logger.info("Created commit #{message}")
  else
    if output =~ /nothing\sto\scommit/
      logger.info("Nothing to commit")
    else
      logger.error output
    end
    return false
  end
end

#clone(url, path) ⇒ Rugged::Repository

Clones the url if the clone path already exists, nothing is done

Parameters:

  • branch (String)
    • the name of the branch you want checked out when cloning

  • url (String)
    • the url to clone

Returns:

  • (Rugged::Repository)
    • the clond repository



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/release_manager/git/utilites.rb', line 42

def clone(url, path)
  if File.exists?(File.join(path, '.git'))
    add_remote(url, 'upstream')
    fetch('upstream')
    repo
  else
    logger.info("Cloning repo with url: #{url} to #{path}")
    r = Rugged::Repository.clone_at(url, path, {
        #progress: lambda { |output| logger.debug output },
        credentials: credentials.call(url)
    })
    r
  end
end

#create_branch(name, target = 'upstream/master') ⇒ Rugged::Branch

we should be creating the branch from upstream

Returns:

  • (Rugged::Branch)


110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/release_manager/git/utilites.rb', line 110

def create_branch(name, target = 'upstream/master')
  # fetch the remote if defined in the target
  unless branch_exist?(name)
    remote_name, ref = target.split('/', 2)
    fetch(remote_name)
    logger.info("Creating branch: #{name} for #{path} from #{target}")
    found_ref = find_ref(target)
    repo.create_branch(name, found_ref)
  else
    repo.branches[name]
  end
end

#create_commit(message) ⇒ Object

Parameters:

  • message (String)
    • the message you want in the commit



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/release_manager/git/utilites.rb', line 334

def create_commit(message)
  unless author_name and author_email
    raise GitError.new("Git username and email must be set, current: #{author.inspect}")
  end
  # get the index for this repository
  repo.status { |file, status_data| logger.debug "#{file} has status: #{status_data.inspect}" }
  index = repo.index
  options = {}
  options[:author] = author
  options[:message] = message
  options[:committer] = author
  options[:parents] = repo.empty? ? [] : [repo.head.target].compact
  options[:update_ref] = 'HEAD'
  options[:tree] = index.write_tree
  index.write
  oid = Rugged::Commit.create(repo, options)
  if oid
    logger.info("Created commit #{message}")
    repo.status { |file, status_data| logger.debug "#{file} has status: #{status_data.inspect}" }
  else
    logger.warn("Something went wrong with the commit")
  end
  oid
end

#create_diff(src_ref, dst_ref) ⇒ Rugged::Diff

Returns a rugged diff object between src and dst.

Parameters:

  • src_ref (Rugged::Object)
    • the rugged object or string to compare from

  • dst_ref (Rugged::Object)
    • the rugged object or string to compare to

Returns:

  • (Rugged::Diff)

    a rugged diff object between src and dst



433
434
435
436
437
438
439
440
# File 'lib/release_manager/git/utilites.rb', line 433

def create_diff(src_ref, dst_ref)
  logger.debug("Creating a diff between #{dst_ref} and #{src_ref}")
  src = repo.lookup(find_ref(src_ref))
  src = src.kind_of?(Rugged::Tag::Annotation) ? src.target : src
  dst = repo.lookup(find_ref(dst_ref))
  dst = dst.kind_of?(Rugged::Tag::Annotation) ? dst.target : dst
  dst.diff(src)
end

#create_diff_obj(src, dst) ⇒ Object

with status, old_path, new_path, and content status can be one of: :added, :deleted, :modified, :renamed, :copied, :ignored, :untracked, :typechange

Parameters:

  • src (Rugged::Object)
    • the rugged object or string to compare from

  • dst (Rugged::Object)
    • the rugged object or string to compare to



421
422
423
424
425
426
427
428
# File 'lib/release_manager/git/utilites.rb', line 421

def create_diff_obj(src, dst)
  diff = create_diff(src, dst)
  diff.deltas.map do |d|
    { old_path: d.old_file[:path], status: d.status,
      new_path: d.new_file[:path], content: get_content(d.new_file[:oid])
    }
  end
end

#create_local_tag(name, ref, message = nil) ⇒ Object

Parameters:

  • name (String)
    • the name of the tag

  • ref (String)
    • the ref oid the tag should point to

  • message (String) (defaults to: nil)
    • optional tag message



196
197
198
199
200
# File 'lib/release_manager/git/utilites.rb', line 196

def create_local_tag(name, ref, message = nil)
  message ||= name
  logger.info("Creating tag #{name} which points to #{ref}")
  repo.tags.create(name, ref, {:message => message} )
end

#credentialsObject



33
34
35
# File 'lib/release_manager/git/utilites.rb', line 33

def credentials
  @credentials ||= ReleaseManager::Git::Credentials.new(nil)
end

#current_branchString

Returns the name of the current branch.

Returns:

  • (String)

    the name of the current branch



183
184
185
# File 'lib/release_manager/git/utilites.rb', line 183

def current_branch
  repo.head.name.sub(/^refs\/heads\//, '')
end

#current_branch?(name) ⇒ Boolean

Returns true if the current branch is the name

Parameters:

  • name (String)
    • the name of the branch

Returns:

  • (Boolean)

    returns true if the current branch is the name



189
190
191
# File 'lib/release_manager/git/utilites.rb', line 189

def current_branch?(name)
  current_branch != name
end

#delete_branch(name) ⇒ Object

deletes the branch with the given name

Parameters:

  • name (String)
    • the name of the branch to delete



125
126
127
128
# File 'lib/release_manager/git/utilites.rb', line 125

def delete_branch(name)
  repo.branches.delete(name)
  !branch_exist?(name)
end

#fetch(remote_name = 'upstream', tags = false) ⇒ Object

Parameters:

  • remote_name (String) (defaults to: 'upstream')
    • the name of the remote



14
15
16
17
18
19
20
21
22
23
# File 'lib/release_manager/git/utilites.rb', line 14

def fetch(remote_name = 'upstream', tags = false)
  remote_name ||= 'upstream'
  return unless remote_exists?(remote_name)
  remote = repo.remotes[remote_name]
  options = {credentials: credentials.call(remote.url)}
  logger.info("Fetching remote #{remote_name} from #{remote.url}")
  options[:certificate_check] = lambda { |valid, host| true } if ENV['GIT_SSL_NO_VERIFY']
  fetch_cli(remote_name) # helps get tags
  remote.fetch(options)
end

#fetch_cli(remote) ⇒ Object

Note:

this is a hack to get around libgit2 inability to get remote tags

Parameters:

  • - (String)

    the name of the remote to fetch from



292
293
294
# File 'lib/release_manager/git/utilites.rb', line 292

def fetch_cli(remote)
  `#{git_command} fetch #{remote} 2>&1 > /dev/null`
end

#find_or_create_remote(remote_name) ⇒ Rugged::Remote

find the remote or create a new remote with the name as source

Parameters:

  • remote_name (String)
    • the remote name

Returns:

  • (Rugged::Remote)

    the remote object



219
220
221
222
223
# File 'lib/release_manager/git/utilites.rb', line 219

def find_or_create_remote(remote_name)
  remote_from_name(remote_name) ||
      remote_from_url(remote_name) ||
      add_remote(remote_name, 'source', true)
end

#find_ref(sha_or_ref) ⇒ String

Returns the oid of the sha or ref.

Parameters:

  • sha_or_ref (String)
    • the name or sha of the ref

Returns:

  • (String)

    the oid of the sha or ref



454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/release_manager/git/utilites.rb', line 454

def find_ref(sha_or_ref)
  case sha_or_ref
    when Rugged::Object
      sha_or_ref.oid
    else
      begin
        repo.rev_parse_oid(sha_or_ref)
      rescue Rugged::ReferenceError => e
        tag = repo.tags.find{|t| t.name == sha_or_ref.split('/').last}
        repo.rev_parse_oid(tag.target.oid) if tag
      end
  end
end

#find_tag(id) ⇒ Rugged::Tag

Parameters:

  • id (String)
    • the tag name or oid

Returns:

  • (Rugged::Tag)


149
150
151
152
153
154
155
# File 'lib/release_manager/git/utilites.rb', line 149

def find_tag(id)
  if id.length >= 40
    repo.tags.find {|t| t.target.oid == id  }
  else
    repo.tags.find {|t| t.name == id }
  end
end

#get_content(oid) ⇒ String

Returns the contents of the file from the object.

Parameters:

  • oid (String)

    the oid of the file object

Returns:

  • (String)

    the contents of the file from the object



410
411
412
413
414
# File 'lib/release_manager/git/utilites.rb', line 410

def get_content(oid)
  return nil if oid =~ /0000000000000000000000000000000000000000/
  obj = repo.read(oid)
  obj.data
end

#git_commandString

Returns the git command with.

Returns:

  • (String)

    the git command with



321
322
323
# File 'lib/release_manager/git/utilites.rb', line 321

def git_command
  @git_command ||= "git --work-tree=#{path} --git-dir=#{repo.path}"
end

#git_url?(name) ⇒ MatchData

Is the name actually a url?

Parameters:

  • name (String)
    • the remote name or url to check

Returns:

  • (MatchData)

    MatchData if the remote name is a url



242
243
244
# File 'lib/release_manager/git/utilites.rb', line 242

def git_url?(name)
  /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|\#[-\d\w._]+?)$/.match(name)
end

#push_branch(remote_name, branch, force = false) ⇒ Object

Parameters:

  • remote_name (String)
    • the remote name to push the branch to

Raises:



131
132
133
134
135
136
137
138
139
140
# File 'lib/release_manager/git/utilites.rb', line 131

def push_branch(remote_name, branch, force = false)
  remote = find_or_create_remote(remote_name)
  raise RemoteNotFound, "Remote named: #{remote_name} was not found" unless remote
  b = repo.branches[branch]
  raise InvalidBranchName.new("Branch #{branch} does not exist locally, cannot push") unless b
  refs = [b.canonical_name]
  refs = refs.map { |r| r.prepend('+') } if force
  logger.info("Pushing branch #{branch} to remote #{remote.url}")
  remote.push(refs, credentials: credentials)
end

#push_tags(remote_name, id = nil) ⇒ Object

push all the tags to the remote

Parameters:

  • remote_name (String)
    • the remote name to push tags to

  • id (String) (defaults to: nil)
    • a ref spec to push, set to nil to push all

Raises:



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/release_manager/git/utilites.rb', line 166

def push_tags(remote_name, id = nil)
  remote = find_or_create_remote(remote_name)
  raise RemoteNotFound, "Remote named: #{remote_name} was not found" unless remote
  all_refs = repo.tags.map(&:canonical_name)
  refs = if id
           tag = find_tag(id)
           tag.canonical_name if tag
         else
           all_refs
         end
  raise NoTagsExists, "No tags were pushed" if refs.empty?
  logger.debug("Pushing refs #{refs}")
  logger.info("Pushing tags to remote #{remote.url}")
  remote.push(refs, credentials: credentials)
end

#rebase_branch(branch_name, target, remote = nil) ⇒ Object

TODO: change this to rugged implementation

Parameters:

  • branch_name (String)
    • the branch name you want to update

  • target (String)
    • the target branch you want to rebase against

  • remote (String) (defaults to: nil)
    • the remote name to rebase from, defaults to local



382
383
384
385
386
387
388
389
390
# File 'lib/release_manager/git/utilites.rb', line 382

def rebase_branch(branch_name, target, remote = nil)
  src = [remote, target].compact.join('/') # produces upstream/master
  Dir.chdir(path) do
    checkout_branch(branch_name)
    logger.info("Rebasing #{branch_name} with #{src}")
    output = `#{git_command} rebase #{src} 2>&1`
    raise GitError.new(output) unless $?.success?
  end
end

#ref_exists?(sha1_or_ref) ⇒ Boolean

Returns true if the ref exists.

Parameters:

  • sha_or_ref (String)
    • the name or sha of the ref

Returns:

  • (Boolean)

    true if the ref exists



444
445
446
447
448
449
450
# File 'lib/release_manager/git/utilites.rb', line 444

def ref_exists?(sha1_or_ref)
  begin
    find_ref(sha1_or_ref)
  rescue Rugged::ReferenceError => e
    false
  end
end

#remote_exists?(name) ⇒ Boolean

Returns - return true if the remote name and url are defined in the git repo.

Parameters:

  • name (String)
    • the name of the remote

Returns:

  • (Boolean)
    • return true if the remote name and url are defined in the git repo



82
83
84
# File 'lib/release_manager/git/utilites.rb', line 82

def remote_exists?(name)
  repo.remotes[name]
end

#remote_from_name(name) ⇒ Rugged::Remote

Given the url find the remote with that url

Parameters:

  • name (String)
    • the remote name to push the branch to

Returns:

  • (Rugged::Remote)

    the remote object if found



228
229
230
# File 'lib/release_manager/git/utilites.rb', line 228

def remote_from_name(name)
  repo.remotes.find { |r| r.name.eql?(name) } unless git_url?(name)
end

#remote_from_url(url) ⇒ Rugged::Remote

Given the url find the remote with that url

Parameters:

  • url (String)
    • the remote url to push the branch to

Returns:

  • (Rugged::Remote)

    the remote object if found



235
236
237
# File 'lib/release_manager/git/utilites.rb', line 235

def remote_from_url(url)
  repo.remotes.find { |r| r.url.eql?(url) } if git_url?(url)
end

#remote_url_matches?(name, url) ⇒ Boolean

Returns - true if the url matches a remote url already defined.

Parameters:

  • name (String)
    • the name of the remote

  • url (String)
    • the url of the remote

Returns:

  • (Boolean)
    • true if the url matches a remote url already defined



89
90
91
# File 'lib/release_manager/git/utilites.rb', line 89

def remote_url_matches?(name, url)
  repo.remotes[name].url.eql?(url)
end

#remove_file(file) ⇒ Object

Parameters:

  • file (String)
    • the path to the file you want to remove



326
327
328
329
330
331
# File 'lib/release_manager/git/utilites.rb', line 326

def remove_file(file)
  logger.debug("Removing file #{file}")
  index = repo.index
  File.unlink(file)
  index.remove(file)
end

#repoObject



9
10
11
# File 'lib/release_manager/git/utilites.rb', line 9

def repo
  @repo ||= Rugged::Repository.new(path)
end

#tag_exists?(name) ⇒ Boolean

Returns - return true if the tag exists.

Parameters:

  • name (String)
    • the name of the tag to check for existence

Returns:

  • (Boolean)
    • return true if the tag exists



159
160
161
# File 'lib/release_manager/git/utilites.rb', line 159

def tag_exists?(name)
 tags.include?(name)
end

#tagsArray

Returns - returns an array of tag names.

Returns:

  • (Array)
    • returns an array of tag names



143
144
145
# File 'lib/release_manager/git/utilites.rb', line 143

def tags
  repo.tags.map(&:name)
end

#transportsObject



25
26
27
28
29
30
31
# File 'lib/release_manager/git/utilites.rb', line 25

def transports
  [:ssh, :https].each do |transport|
    unless ::Rugged.features.include?(transport)
      logger.warn("Rugged has been compiled without support for %{transport}; Git repositories will not be reachable via %{transport}.  Try installing libssh-devel") % {transport: transport}
    end
  end
end

#up2date?(src_ref, dst_ref) ⇒ Boolean

Returns:

  • (Boolean)


316
317
318
# File 'lib/release_manager/git/utilites.rb', line 316

def up2date?(src_ref, dst_ref)
  create_diff(src_ref, dst_ref).deltas.count < 1
end