Module: Git

Defined in:
lib/git.rb

Class Method Summary collapse

Class Method Details

.all_branchesObject

Returns an array of all local branch names



86
87
88
89
90
91
92
# File 'lib/git.rb', line 86

def self.all_branches()
   `git for-each-ref --sort=-committerdate --format='%(refname)' refs/heads refs/remotes`.
   split("\n").
   map {|branch| branch.sub(/refs\/\w+\//, '') }.
   uniq.
   reject {|branch| branch =~ %r{\w+/HEAD} }
end

.branch_hash(branch) ⇒ Object

Returns the SHA1 hash that the specified branch or symbol points to



115
116
117
# File 'lib/git.rb', line 115

def self.branch_hash(branch)
   `git rev-parse --verify --quiet #{branch.shellescape} 2>/dev/null`.strip
end

.branch_info(branch) ⇒ Object

Returns formatted string containing:

commit_hash Author Name (relative date)

for the specified branch or commit



124
125
126
127
128
129
130
# File 'lib/git.rb', line 124

def self.branch_info(branch)
   # branch info format: hash author (relative date)
   format = "%h %an %Cgreen(%ar)%Creset"
   branch_info = `git show -s --pretty=#{format.shellescape} #{branch.shellescape}`.strip
   simple_branch = branch.sub('origin/', '')
   sprintf "%-30s %s", simple_branch, branch_info
end

.branches_not_merged_into(branch) ⇒ Object

Returns an array of branches that aren’t merged into the specified branch



58
59
60
# File 'lib/git.rb', line 58

def self.branches_not_merged_into(branch)
   self::all_branches - self::merged_branches(branch)
end

.commit_message(ref) ⇒ Object

Returns the commit message from the given commit hash or branch name



244
245
246
# File 'lib/git.rb', line 244

def self.commit_message(ref)
   `git log -1 --format="%B" #{ref.shellescape}`.strip
end

.current_branchObject

Returns the name of the currently checked out branch, or nil if detached.



95
96
97
# File 'lib/git.rb', line 95

def self.current_branch()
   `git symbolic-ref -q --short HEAD`.strip
end

.delete_current_branchObject

Deletes the current branch. For cleaning up after errors.



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/git.rb', line 100

def self.delete_current_branch()
   devBranch = get_branch('development')
   branch = Git::current_branch

   if branch == devBranch
      puts "Cannot remove development branch"
      exit 1
   end

   Git::switch_branch(devBranch)

   `git branch -d #{branch.shellescape}`.strip
end

.editorObject

Returns the editor specified in the user’s gitconfig.



22
23
24
25
26
27
28
29
# File 'lib/git.rb', line 22

def self.editor
   editor = `git var GIT_EDITOR`.strip
   unless editor
      abort "Configure an editor for git:\n" +
            "git config --global core.editor vim"
   end
   return editor
end

.feature_branches(type) ⇒ Object

Returns an array of unmerged feature branches



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/git.rb', line 63

def self.feature_branches(type)
   devBranch = get_branch('development')

   branches = if type == :unmerged
      self.branches_not_merged_into(devBranch)
   elsif type == :merged
      self.merged_branches(devBranch)
   else
      raise ArgumentError, 'Must specify :merged or :unmerged in feature_branches.'
   end

   branches
end

.get_branch(branch = '') ⇒ Object

Return the specified branch in the feature git config section.



12
13
14
15
16
17
18
19
# File 'lib/git.rb', line 12

def self.get_branch(branch = '')
   specified_branch = `git config feature.#{branch.shellescape}-branch`.strip
   if specified_branch.empty? || $? != 0
      die("No #{branch} branch specified; set it with:\n" +
       "git config feature.#{branch}-branch \"name-of-branch\"")
   end
   return specified_branch
end

.get_description_from_user(initial_message = '') ⇒ Object

Starts an editor with a file. Returns a string with the contents of that file.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/git.rb', line 33

def self.get_description_from_user(initial_message = '')
   require 'tempfile'
   editor = self::editor

   file = Tempfile.new('merge-msg')
   file.print(initial_message)
   file.flush

   if editor == 'vim'
      params = "'+set ft=gitcommit' '+set textwidth=72'" +
       " '+setlocal spell spelllang=en_us'"
   else
      params = ''
   end
   pid = spawn("#{editor} #{params} #{file.path}")
   Process.wait pid

   file.rewind
   commit = file.read
   file.unlink

   return commit
end

.git_dirObject



248
249
250
# File 'lib/git.rb', line 248

def self.git_dir()
   `git rev-parse --git-dir`.strip
end

.has_uncommitted_changesObject



2
3
4
5
# File 'lib/git.rb', line 2

def self.has_uncommitted_changes()
   clean = system("git diff --quiet 2>/dev/null >&2")
   return !clean
end

.in_a_repoObject



7
8
9
# File 'lib/git.rb', line 7

def self.in_a_repo
   return system("git rev-parse")
end

.merged_branches(into_branch = 'master') ⇒ Object

Returns an array of all branch names that have have been merged into the specified branch



79
80
81
82
83
# File 'lib/git.rb', line 79

def self.merged_branches(into_branch='master')
   `git branch --merged #{into_branch.shellescape} -a`.
      split("\n").
      map {|branch| branch.gsub('*','').strip.sub('remotes/','')}
end

.run_safe(commands) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/git.rb', line 132

def self.run_safe(commands)
   while command = commands.shift
      safe_command = command.gsub(/[^[:print:]]+/,' ')
      puts "> " + safe_command
      unless system(command)
         puts highlight("\nERROR: failed on #{safe_command}.")
         puts "\nWould have run:"
         commands.each do |command|
            puts "# " + command.gsub(/[^[:print:]]+/,' ')
         end
         abort
      end
   end
end

.show_branch_list(options = {}) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/git.rb', line 160

def self.show_branch_list(options = {})
   puts "\nCurrent Branch:"
   puts "--" * 30
   current = Git::current_branch
   print HIGHLIGHT
   if current
      print Git::branch_info(current)
   else
      print "(not on any branch!)"
   end
   puts HIGHLIGHT_OFF

   options.each do |branch_type, branches|
      puts "\nAvailable #{branch_type} branches:"
      puts "--" * 30
      if branches && !branches.empty?
         shown_branches = {}
         branches.each do |branch|
            simple_branch = branch.sub('origin/', '')
            next if shown_branches.has_key?(simple_branch)
            puts Git::branch_info(branch)
            shown_branches[simple_branch] = true
         end
      else
         puts "(none)"
      end
   end
end

.show_stashes_saved_on(branch = nil) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/git.rb', line 147

def self.show_stashes_saved_on(branch = nil)
   self.stashes.each do |stash|
      if !branch || stash[:branch] == branch
         puts "=" * 40
         puts highlight(
            "There is a stash saved from #{branch} #{stash[:date]}")
         puts wrap_text(stash[:subject])
         puts "see it with >\n git stash show -p " + stash[:ref]
         puts "apply it with >\n git stash apply " + stash[:ref]
      end
   end
end

.stashesObject



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/git.rb', line 189

def self.stashes
   # Do we even have a stash?
   unless File.exist? '.git/refs/stash'
      return []
   end

   # format = "relative date|stash ref|commit message"
   `git log --format="%ar|%gd|%s" -g "refs/stash"`.lines.map do |line|
      fields = line.split '|', 3
      # All stashes have commit messages like "WIP on branch_name: ..."
      branch = line[/\S+:/]
      {
         :date => fields[0],
         :ref => fields[1],
         :branch => branch && branch.chop,
         :subject =>fields[2]
      }
   end
end

.submodules_update(mode = "") ⇒ Object

Update / initialize submodules from the TLD or return the command that would do so as a string.



229
230
231
232
233
234
235
236
237
238
239
# File 'lib/git.rb', line 229

def self.submodules_update(mode = "")
   # capture only the path, not the newline
   basedir = `git rev-parse --show-toplevel`.split("\n").first
   command = "cd #{basedir.shellescape} && git submodule --quiet update --init --recursive"

   if mode == "get"
      return command
   else
      Git::run_safe([command])
   end
end

.switch_branch(branch) ⇒ Object

Switch to the specified branch. Because we use submodules, we have to check for updates to those submodules when we checkout a branch.

args: –clean - remove every unstaged file, including non-existant submodules



217
218
219
220
221
222
223
# File 'lib/git.rb', line 217

def self.switch_branch(branch)
   self.run_safe(["git checkout \"#{branch}\""])
   self.submodules_update
   self.run_safe(["git clean -ffd"]) if ARGV.include?('--clean')

   self.show_stashes_saved_on(branch)
end