Class: GitPivotalTrackerIntegration::Util::Git

Inherits:
Object
  • Object
show all
Defined in:
lib/git-pivotal-tracker-integration/util/git.rb

Overview

Utilities for dealing with Git

Constant Summary collapse

KEY_REMOTE =
'remote'.freeze
KEY_ROOT_BRANCH =
'root-branch'.freeze
KEY_ROOT_REMOTE =
'root-remote'.freeze
RELEASE_BRANCH_NAME =
'pivotal-tracker-release'.freeze

Class Method Summary collapse

Class Method Details

.add_hook(name, source, overwrite = false) ⇒ void

This method returns an undefined value.

Adds a Git hook to the current repository



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 33

def self.add_hook(name, source, overwrite = false)
  hooks_directory =  File.join repository_root, '.git', 'hooks'
  hook = File.join hooks_directory, name

  if overwrite || !File.exist?(hook)
    print "Creating Git hook #{name}...  "

    FileUtils.mkdir_p hooks_directory
    File.open(source, 'r') do |input|
      File.open(hook, 'w') do |output|
        output.write(input.read)
        output.chmod(0755)
      end
    end

    puts 'OK'
  end
end

.branch_nameString

Returns the name of the currently checked out branch



55
56
57
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 55

def self.branch_name
  Util::Shell.exec('git branch').scan(/\* (.*)/)[0][0]
end

.create_branch(name, print_messages = true) ⇒ void

This method returns an undefined value.

Creates a branch with a given name. First pulls the current branch to ensure that it is up to date and then creates and checks out the new branch. If specified, sets branch-specific properties that are passed in.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 66

def self.create_branch(name, print_messages = true)
  root_branch = branch_name
  root_remote = get_config KEY_REMOTE, :branch

  print "Pulling #{root_branch}... " if print_messages
  Util::Shell.exec 'git pull --quiet --ff-only'
  puts 'OK' if print_messages

  print "Creating and checking out #{name}... " if print_messages

  Util::Shell.exec "git checkout --quiet -b #{name}"
  set_config KEY_ROOT_BRANCH, root_branch, :branch
  set_config KEY_ROOT_REMOTE, root_remote, :branch
  puts 'OK' if print_messages
end

.create_commit(message, story) ⇒ void

This method returns an undefined value.

Creates a commit with a given message. The commit includes all change files.



90
91
92
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 90

def self.create_commit(message, story)
  Util::Shell.exec "git commit --quiet --all --allow-empty --message \"#{message}\n\n[##{story.id}]\""
end

.create_release_tag(name, story) ⇒ void

This method returns an undefined value.

Creates a tag with the given name. Before creating the tag, commits all outstanding changes with a commit message that reflects that these changes are for a release.



102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 102

def self.create_release_tag(name, story)
  root_branch = branch_name

  print "Creating tag v#{name}... "

  create_branch RELEASE_BRANCH_NAME, false
  create_commit "#{name} Release", story
  Util::Shell.exec "git tag v#{name}"
  Util::Shell.exec "git checkout --quiet #{root_branch}"
  Util::Shell.exec "git branch --quiet -D #{RELEASE_BRANCH_NAME}"

  puts 'OK'
end

.get_config(key, scope = :inherited) ⇒ String

Returns a Git configuration value. This value is read using the git config command. The scope of the value to read can be controlled with the scope parameter.

Raises:

  • if the specified scope is not :branch or :inherited



126
127
128
129
130
131
132
133
134
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 126

def self.get_config(key, scope = :inherited)
  if :branch == scope
    Util::Shell.exec("git config branch.#{branch_name}.#{key}", false).strip
  elsif :inherited == scope
    Util::Shell.exec("git config #{key}", false).strip
  else
    raise "Unable to get Git configuration for scope '#{scope}'"
  end
end

.merge(story, no_complete) ⇒ void

This method returns an undefined value.

Merges the current branch to its root branch and deletes the current branch



141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 141

def self.merge(story, no_complete)
  development_branch = branch_name
  root_branch = get_config KEY_ROOT_BRANCH, :branch

  print "Merging #{development_branch} to #{root_branch}... "
  Util::Shell.exec "git checkout --quiet #{root_branch}"
  Util::Shell.exec 'git pull --quiet --ff-only'
  Util::Shell.exec "git merge --quiet --no-ff -m \"Merge #{development_branch} to #{root_branch}\n\n[#{no_complete ? '' : 'Completes '}##{story.id}]\" #{development_branch}"
  puts 'OK'

  print "Deleting #{development_branch}... "
  Util::Shell.exec "git branch --quiet -D #{development_branch}"
  puts 'OK'
end

.push(*refs) ⇒ void

This method returns an undefined value.

Push changes to the remote of the current branch



160
161
162
163
164
165
166
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 160

def self.push(*refs)
  remote = get_config KEY_REMOTE, :branch

  print "Pushing to #{remote}... "
  Util::Shell.exec "git push --quiet #{remote} " + refs.join(' ')
  puts 'OK'
end

.repository_rootString

Returns the root path of the current Git repository. The root is determined by ascending the path hierarchy, starting with the current working directory (+Dir#pwd+), until a directory is found that contains a .git/ sub directory.

Raises:

  • if the current working directory is not in a Git repository



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 175

def self.repository_root
  repository_root = Dir.pwd

  until Dir.entries(repository_root).any? { |child| File.directory?(child) && (child =~ /^.git$/) }
    next_repository_root = File.expand_path('..', repository_root)
    abort('Current working directory is not in a Git repository') unless repository_root != next_repository_root
    repository_root =  next_repository_root
  end

  repository_root
end

.set_config(key, value, scope = :local) ⇒ void

This method returns an undefined value.

Sets a Git configuration value. This value is set using the git config command. The scope of the set value can be controlled with the scope parameter.

Raises:

  • if the specified scope is not :branch, :global, or :local



199
200
201
202
203
204
205
206
207
208
209
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 199

def self.set_config(key, value, scope = :local)
  if :branch == scope
    Util::Shell.exec "git config --local branch.#{branch_name}.#{key} #{value}"
  elsif :global == scope
    Util::Shell.exec "git config --global #{key} #{value}"
  elsif :local == scope
    Util::Shell.exec "git config --local #{key} #{value}"
  else
    raise "Unable to set Git configuration for scope '#{scope}'"
  end
end

.trivial_merge?void

This method returns an undefined value.

Checks whether merging the current branch back to its root branch would be a trivial merge. A trivial merge is defined as one where the net change of the merge would be the same as the net change of the branch being merged. The easiest way to ensure that a merge is trivial is to rebase a development branch onto the tip of its root branch.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git-pivotal-tracker-integration/util/git.rb', line 218

def self.trivial_merge?
  development_branch = branch_name
  root_branch = get_config KEY_ROOT_BRANCH, :branch

  print "Checking for trivial merge from #{development_branch} to #{root_branch}... "

  Util::Shell.exec "git checkout --quiet #{root_branch}"
  Util::Shell.exec 'git pull --quiet --ff-only'
  Util::Shell.exec "git checkout --quiet #{development_branch}"

  root_tip = Util::Shell.exec "git rev-parse #{root_branch}"
  common_ancestor = Util::Shell.exec "git merge-base #{root_branch} #{development_branch}"

  if root_tip != common_ancestor
    abort "\n#{root_branch} branch is ahead of your #{development_branch} branch. \nSo please merge #{root_branch} to #{development_branch} and resolve any conflicts if any. Run 'git merge #{root_branch}' and try git finish again."
  end

  puts 'OK'
end