Module: Git::BranchStats

Defined in:
lib/git-branch_stats.rb,
lib/git-branch_stats/version.rb

Constant Summary collapse

VERSION =
"0.0.3"

Class Method Summary collapse

Class Method Details

.analyze(branch = `git rev-parse --abbrev-ref HEAD`.strip) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/git-branch_stats.rb', line 67

def self.analyze(branch = `git rev-parse --abbrev-ref HEAD`.strip)
  warnings = []
  # Get all commits that are only visible for this HEAD (branch).
  # List commits, without merges, of HEAD, that are not any ref from other branches
  branches = `git branch`.split(/\n/)
  independent_commits_and_emails = if branches.one?
    independent_commits_and_emails = `git log --format="%H,%ae" --no-merges #{branch}`.split(/\n/)
  else
    independent_commits_and_emails = `git log --format="%H,%ae" --no-merges #{branch} --not $(git for-each-ref --format="%(refname)" refs/heads | grep -Fv refs/heads/#{branch})`.split(/\n/)
  end
  independent_commits = independent_commits_and_emails.collect {|c| c.split(',').first}
  emails = independent_commits_and_emails.collect {|c| c.split(',').last}
  emails = emails.uniq

  # Using the list of independent commits, gather stats

  if independent_commits.any?
    # Start analysis before the last independent commit
    diff_origin = independent_commits.last + "~"
    commit_count = independent_commits.length
    # Fallback if last independent commit is the first commit of the repository
    # NOTE: Running the analyzer on a branch that has independent commits up to the first commit
    # will give weird results since it will omit this first commit in the numstats and language analysis.
    `git rev-parse --verify --quiet #{diff_origin}`
    if $?.to_i > 0
      diff_origin = independent_commits.last

      warnings.push "WARN: Branch has independent commits since the first commit of the repo, stats will be skewed (can't run stats *before* the first commit)"
      # In this case, exclude the last commit (since it won't be included in the stats)
      commit_count = commit_count - 1
    end

    # Numstats (files, +/-)
    numstats = `git diff --numstat #{diff_origin}`.split(/\n/)
    stats_by_file = numstats.collect do |numstat|
      additions, deletions, filename = numstat.split(/\t/)
      {:additions => additions.to_i, :deletions => deletions.to_i, :filename => filename}
    end

    # Extract content from diffs to generate language stats (only consider diffs that contain added/changed stuff)
    stats_by_file.select {|numstat| numstat[:additions] > 0}.each do |numstat|
      # Since first independent commit
      diff = `git diff #{diff_origin} -- #{numstat[:filename]}`
      # Collect lines prefixed with a single +
      new_content = diff.lines.select {|line| line =~ /^\+[^\+]/}.collect {|line| line[1..-1]}.join("\n")
      # Store in the stats for later language analysis
      numstat[:content] = new_content
    end

    # Extract language stats using github's linguist gem
    language_stats = extract_language_stats(stats_by_file)

    # Normalize stats for json REST API
    change_count = stats_by_file.length
    total_additions = stats_by_file.inject(0) {|total, numstat| total += numstat[:additions]}
    total_deletions = stats_by_file.inject(0) {|total, numstat| total += numstat[:deletions]}
  else
    commit_count = 0
    total_additions = 0
    total_deletions = 0
    change_count = 0
    language_stats = []
    emails = []
    warnings.push "No independent commits yet, commit something!"
  end

  {
    :commits => commit_count,
    :additions => total_additions,
    :deletions => total_deletions,
    :files_changed => change_count,
    :language_stats => language_stats,
    :emails => emails,
    :warnings => warnings
  }
end

.extract_language_stats(stats_by_file) ⇒ Object



58
59
60
61
62
63
64
# File 'lib/git-branch_stats.rb', line 58

def self.extract_language_stats(stats_by_file)
  file_blobs = stats_by_file.select {|numstat| numstat[:additions] > 0}.collect {|numstat| ExcerptBlob.new('./' + numstat[:filename], numstat[:content])}
  linguist = AllTypesRepository.new(file_blobs)
  # Linguist wraps the language in an object, we only want the language name (and why not, sort it by size, descending)
  language_stats = linguist.languages.collect {|language, size| [language.name, size]}.sort_by {|language, size| -size}
  language_stats
end