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



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

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



135
136
137
# File 'lib/git_commit_notifier/git.rb', line 135

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

.branch_headsObject



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

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:



74
75
76
77
78
# File 'lib/git_commit_notifier/git.rb', line 74

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:



54
55
56
# File 'lib/git_commit_notifier/git.rb', line 54

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.



17
18
19
20
21
# File 'lib/git_commit_notifier/git.rb', line 17

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

.git_dirObject



119
120
121
# File 'lib/git_commit_notifier/git.rb', line 119

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:



27
28
29
30
31
32
# File 'lib/git_commit_notifier/git.rb', line 27

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



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

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:



64
65
66
# File 'lib/git_commit_notifier/git.rb', line 64

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.



270
271
272
273
274
# File 'lib/git_commit_notifier/git.rb', line 270

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



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
192
193
# File 'lib/git_commit_notifier/git.rb', line 164

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.



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

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.



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

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.



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

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



127
128
129
# File 'lib/git_commit_notifier/git.rb', line 127

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

.rev_type(rev) ⇒ Object



195
196
197
198
199
# File 'lib/git_commit_notifier/git.rb', line 195

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:



87
88
89
90
# File 'lib/git_commit_notifier/git.rb', line 87

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



131
132
133
# File 'lib/git_commit_notifier/git.rb', line 131

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:



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

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:



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

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



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

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
11
12
# File 'lib/git_commit_notifier/git.rb', line 6

def to_utf8(str)
  return str  unless str.respond_to?(:force_encoding)
  str = str.force_encoding(Encoding::UTF_8)
  return str  if str.valid_encoding?
  str = str.force_encoding(Encoding::BINARY)
  str.encode("UTF-8", :invalid => :replace, :undef => :replace)
end

.toplevel_dirObject



123
124
125
# File 'lib/git_commit_notifier/git.rb', line 123

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