Class: GitCommitNotifier::Git

Inherits:
Object
  • Object
show all
Defined in:
lib/git_commit_notifier/git.rb

Overview

Git methods

Class Method Summary collapse

Class Method Details

.branch_commits(treeish) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/git_commit_notifier/git.rb', line 104

def branch_commits(treeish)
  args = branch_heads - [ branch_head(treeish) ]
  args.map! { |tree| "^#{tree}" }
  args << treeish
  lines = lines_from_shell("git rev-list #{args.join(' ')}")
  lines.to_a.map { |commit| commit.chomp }
end

.branch_head(treeish) ⇒ Object



133
134
135
# File 'lib/git_commit_notifier/git.rb', line 133

def branch_head(treeish)
  from_shell("git rev-parse #{treeish}").strip
end

.branch_headsObject



112
113
114
115
# File 'lib/git_commit_notifier/git.rb', line 112

def branch_heads
  lines = lines_from_shell("git rev-parse --branches")
  lines.to_a.map { |head| head.chomp }
end

.changed_files(rev1, rev2) ⇒ Array(String)

Note:

uses "--pretty=oneline" and "--name-status" and "-M" options.

Runs git log and extract filenames only

Parameters:

  • rev1 (String)

    First revision

  • rev2 (String)

    Second revision

Returns:

  • (Array(String))

    File names

See Also:



72
73
74
75
76
# File 'lib/git_commit_notifier/git.rb', line 72

def changed_files(rev1, rev2)
  lines = lines_from_shell("git log #{rev1}..#{rev2} --name-status --pretty=oneline -M#{GitCommitNotifier::CommitHook.config['similarity_detection_threshold'] || "0.5"}")
  lines = lines.select { |line| line =~ /^\w{1}\s+\w+/ } # grep out only filenames
  lines.uniq
end

.describe(rev) ⇒ String

Runs `git describe'

Parameters:

  • rev (String)

    Revision

Returns:

  • (String)

    Its output

See Also:



52
53
54
# File 'lib/git_commit_notifier/git.rb', line 52

def describe(rev)
  from_shell("git describe --always #{rev.strip}").strip
end

.from_shell(cmd) ⇒ String

Runs specified command and gets its output.

Returns:

  • (String)

    Shell command STDOUT (forced to UTF-8)

Raises:

  • (ArgumentError)

    when command exits with nonzero status.



15
16
17
18
19
# File 'lib/git_commit_notifier/git.rb', line 15

def from_shell(cmd)
  r = `#{cmd}`
  raise ArgumentError.new("#{cmd} failed")  unless $?.exitstatus.zero?
  to_utf8(r)
end

.git_dirObject



117
118
119
# File 'lib/git_commit_notifier/git.rb', line 117

def git_dir
  from_shell("git rev-parse --git-dir").strip
end

.lines_from_shell(cmd) ⇒ Enumerable(String)

Runs specified command and gets its output as array of lines.

Returns:

  • (Enumerable(String))

    Shell command STDOUT (forced to UTF-8) as enumerable lines.

Raises:

  • (ArgumentError)

    when command exits with nonzero status.

See Also:



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

def lines_from_shell(cmd)
  lines = from_shell(cmd)
  # Ruby 1.9 tweak.
  lines = lines.lines  if lines.respond_to?(:lines)
  lines
end

.list_of_commits_between_current_commit_and_last_tag(tag_name, rev) ⇒ Array

Note:

There have been many complaints about using git describe to obtain this information but, this looked like the best way to obtain the information here. Here is a link http://www.xerxesb.com/2010/git-describe-and-the-tale-of-the-wrong-commits/ discussing, the way git-describe handles the problem of finding the nearest commit with a tag. Looking forward to someone coming up with a better way.

Lists commits between specified rev and closest annotated tag. Uses git describe to obtain information.

Parameters:

  • tag_name (String)

    of the current tag

  • rev (String)

    sha of the commit the tag is associated with

Returns:

  • (Array)

    Commit hashes and their messages



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

def list_of_commits_between_current_commit_and_last_tag(tag_name, rev)
  result = Array.new

  lines = from_shell("git describe --abbrev=0 #{rev}^1 2> /dev/null | cat ").strip # the `cat` is used to suppress the error that might arise when handling the case of the first commit
  if lines.length != 1
    previous_tag = lines
    list_of_commits = lines_from_shell("git log #{previous_tag}..#{tag_name} --format='%H::::::%s'")
    list_of_commits.each do |row|
      result << Array.new(row.split("::::::"))
    end
  end
  result
end

.log(rev1, rev2) ⇒ String

Note:

uses "--pretty=fuller" option.

Runs git log

Parameters:

  • rev1 (String)

    First revision

  • rev2 (String)

    Second revision

Returns:

  • (String)

    Its output

See Also:



62
63
64
# File 'lib/git_commit_notifier/git.rb', line 62

def log(rev1, rev2)
  from_shell("git log --pretty=fuller #{rev1}..#{rev2}").strip
end

.mailing_list_addressString

Note:

mailing list address retrieved through git config hooks.mailinglist call.

Gets mailing list address.

Returns:

  • (String)

    Mailing list address if exists; otherwise nil.



268
269
270
271
272
# File 'lib/git_commit_notifier/git.rb', line 268

def mailing_list_address
  from_shell("git config hooks.mailinglist").strip
rescue ArgumentError
  nil
end

.new_commits(oldrev, newrev, refname, unique_to_current_branch) ⇒ Object



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
188
189
190
191
# File 'lib/git_commit_notifier/git.rb', line 162

def new_commits(oldrev, newrev, refname, unique_to_current_branch)
  # We want to get the set of commits (^B1 ^B2 ... ^oldrev newrev)
  # Where B1, B2, ..., are any other branch
  a = Array.new

  # If we want to include only those commits that are
  # unique to this branch, then exclude commits that occur on
  # other branches
  if unique_to_current_branch
    # Make a set of all branches, not'd (^BCURRENT ^B1 ^B2...)
    not_branches = lines_from_shell("git rev-parse --not --branches")
    a = not_branches.map { |l| l.chomp }

    # Remove the current branch (^BCURRENT) from the set
    current_branch = rev_parse(refname)
    a.delete_at a.index("^#{current_branch}") unless a.index("^#{current_branch}").nil?
  end

  # Add not'd oldrev (^oldrev)
  a.push("^#{oldrev}")  unless oldrev =~ /^0+$/

  # Add newrev
  a.push(newrev)

  # We should now have ^B1... ^oldrev newrev

  # Get all the commits that match that specification
  lines = lines_from_shell("git rev-list --reverse #{a.join(' ')}")
  lines.to_a.map { |l| l.chomp }
end

.repo_nameString

Note:

Tries to gets human readable repository name through git config hooks.emailprefix call. If it's not specified then returns directory name (except '.git' suffix if exists).

Gets repository name.

Returns:

  • (String)

    Human readable repository name.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/git_commit_notifier/git.rb', line 217

def repo_name
  git_prefix = begin
    from_shell("git config hooks.emailprefix").strip
  rescue ArgumentError
    ''
  end
  return git_prefix  unless git_prefix.empty?
  git_path = toplevel_dir
  # In a bare repository, toplevel directory is empty.  Revert to git_dir instead.
  if git_path.empty?
    git_path = git_dir
  end
  File.expand_path(git_path).split("/").last.sub(/\.git$/, '')
end

.repo_name_realString

Gets repository name.

Returns:

  • (String)

    Repository name.



234
235
236
237
238
239
240
241
# File 'lib/git_commit_notifier/git.rb', line 234

def repo_name_real
  git_path = toplevel_dir
  # In a bare repository, toplevel directory is empty.  Revert to git_dir instead.
  if git_path.empty?
    git_path = git_dir
  end
  File.expand_path(git_path).split("/").last
end

.repo_name_with_parentString

Returns Human readable repository name.

Returns:

  • (String)

    Human readable repository name.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/git_commit_notifier/git.rb', line 248

def repo_name_with_parent
  git_prefix = begin
    from_shell("git config hooks.emailprefix").strip
  rescue ArgumentError
    ''
  end
  return git_prefix  unless git_prefix.empty?
  git_path = toplevel_dir
  # In a bare repository, toplevel directory is empty.  Revert to git_dir instead.
  if git_path.empty?
    git_path = git_dir
  end
  name_with_parent = File.expand_path(git_path).scan(/[a-zA-z0-9\-_]+\/[a-zA-Z0-9\-_]+.git$/).first;
  return name_with_parent.sub(/\.git$/, '')  unless name_with_parent.empty?
  File.expand_path(git_path).split("/").last.sub(/\.git$/, '')
end

.rev_parse(param) ⇒ Object



125
126
127
# File 'lib/git_commit_notifier/git.rb', line 125

def rev_parse(param)
  from_shell("git rev-parse '#{param}'").strip
end

.rev_type(rev) ⇒ Object



193
194
195
196
197
# File 'lib/git_commit_notifier/git.rb', line 193

def rev_type(rev)
  from_shell("git cat-file -t '#{rev}' 2> /dev/null").strip
rescue ArgumentError
  nil
end

.sha_of_filename(rev, filename) ⇒ String

Note:

It was required as when there is a file which is renamed, and it has a 100% similarity index, its sha is not included in the git-show output.

Returns sha1 of the file after the most recent commit. Runs git show #{rev}:#{filename} | git hash-object --stdin to return the sha of the file.

Parameters:

  • rev (String)

    revision where we want to get the sha of the file name

  • filename (String)

    File name whose sha1 we want

Returns:

  • (String)

    sha1 of the file name.

See Also:



85
86
87
88
# File 'lib/git_commit_notifier/git.rb', line 85

def sha_of_filename(rev, filename)
  lines = from_shell("git show  #{rev}:#{filename} | git hash-object --stdin")
  lines.strip
end

.short_commit_id(param) ⇒ Object



129
130
131
# File 'lib/git_commit_notifier/git.rb', line 129

def short_commit_id(param)
  from_shell("git rev-parse --short '#{param}'").strip
end

.show(rev, opts = {}) ⇒ String

Note:

uses "--pretty=fuller" and "-M" option.

Runs git show

Parameters:

  • rev (String)

    Revision

  • opts (Hash) (defaults to: {})

    Options

Options Hash (opts):

  • :ignore_whitespace (String)

    How whitespaces should be treated

Returns:

  • (String)

    Its output

See Also:



39
40
41
42
43
44
45
46
# File 'lib/git_commit_notifier/git.rb', line 39

def show(rev, opts = {})
  gitopt = " --date=rfc2822"
  gitopt += " --pretty=fuller"
  gitopt += " -M#{GitCommitNotifier::CommitHook.config['similarity_detection_threshold'] || "0.5"}"
  gitopt += " -w" if opts[:ignore_whitespace] == 'all'
  gitopt += " -b" if opts[:ignore_whitespace] == 'change'
  from_shell("git show #{rev.strip}#{gitopt}")
end

.split_status(rev1, rev2) ⇒ Hash(Array)

splits the output of changed_files

Parameters:

  • rev1 (String)

    First revision

  • rev2 (String)

    Second revision

Returns:

  • (Hash(Array))

    file names sorted by status

See Also:



95
96
97
98
99
100
101
102
# File 'lib/git_commit_notifier/git.rb', line 95

def split_status(rev1, rev2)
  lines = changed_files(rev1, rev2)
  modified = lines.map { |l| l.gsub(/M\s/,'').strip if l[0,1] == 'M' }.select { |l| !l.nil? }
  added = lines.map { |l| l.gsub(/A\s/,'').strip if l[0,1] == 'A' }.select { |l| !l.nil? }
  deleted = lines.map { |l| l.gsub(/D\s/,'').strip if l[0,1] == 'D' }.select { |l| !l.nil? }
  renamed = lines.map { |l| l.gsub(/R\d+\s/,'').strip if l[0,1] == 'R' }.select { |l| !l.nil? }
  { :m => modified, :a => added, :d => deleted , :r => renamed}
end

.tag_info(refname) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/git_commit_notifier/git.rb', line 199

def tag_info(refname)
  fields = [
    ':tagobject => %(*objectname)',
    ':tagtype => %(*objecttype)',
    ':taggername => %(taggername)',
    ':taggeremail => %(taggeremail)',
    ':subject => %(subject)',
    ':contents => %(contents)'
  ]
  joined_fields = fields.join(",")
  hash_script = from_shell("git for-each-ref --shell --format='{ #{joined_fields} }' #{refname}")
  eval(hash_script)
end

.to_utf8(str) ⇒ Object



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

def to_utf8(str)
  return str  unless str.respond_to?(:force_encoding)
  str.force_encoding(Encoding::UTF_8)
  str
end

.toplevel_dirObject



121
122
123
# File 'lib/git_commit_notifier/git.rb', line 121

def toplevel_dir
  from_shell("git rev-parse --show-toplevel").strip
end