Class: HowIs::Contributions

Inherits:
Object
  • Object
show all
Defined in:
lib/how_is/contributions.rb

Overview

Fetch information about who has contributed to a repository during a given period.

Usage:

github = Github.new()
c = HowIs::Contributions.new(github: github, start_date: '2017-07-01', user: 'how-is', repo: 'how_is')
c.commits          #=> All commits during July 2017.
c.contributors #=> All contributors during July 2017.
c.new_contributors #=> New contributors during July 2017.

Instance Method Summary collapse

Constructor Details

#initialize(github: Fetcher.default_github_instance, start_date:, user:, repo:) ⇒ Contributions

Returns an object that fetches contributor information about a particular repository for a month-long period starting on start_date.

Parameters:

  • github (Github) (defaults to: Fetcher.default_github_instance)

    Github client instance.

  • start_date (String)

    Date in the format YYYY-MM-DD. The first date to include commits from.

  • user (String)

    GitHub user of repository.

  • repo (String)

    GitHub repository name.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/how_is/contributions.rb', line 25

def initialize(github: Fetcher.default_github_instance, start_date:, user:, repo:)
  @github = github

  # IMPL. DETAIL: The external API uses "start_date" so it's clearer,
  #               but internally we use "since_date" to match GitHub's API.

  @since_date = Date.strptime(start_date, "%Y-%m-%d")

  d = @since_date.day
  m = @since_date.month
  y = @since_date.year
  @until_date = Date.new(y, m + 1, d)

  @user = user
  @repo = repo
end

Instance Method Details

#additions_countObject



112
113
114
# File 'lib/how_is/contributions.rb', line 112

def additions_count
  changes["stats"]["additions"]
end

#changed_filesObject



108
109
110
# File 'lib/how_is/contributions.rb', line 108

def changed_files
  changes["files"]
end

#changesObject



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/how_is/contributions.rb', line 79

def changes
  if @stats.nil? || @changed_files.nil?
    @stats = {
      "total" => 0,
      "additions" => 0,
      "deletions" => 0,
    }

    @changed_files = []

    commits.map do |commit|
      commit.stats.each do |k, v|
        @stats[k] += v
      end

      @changed_files += commit.files.map { |file| file["filename"] }
    end

    @changed_files.sort.uniq!
  end

  {"stats" => @stats, "files" => @changed_files}
end

#commit(sha) ⇒ Object



74
75
76
77
# File 'lib/how_is/contributions.rb', line 74

def commit(sha)
  @commit ||= {}
  @commit[sha] ||= @github.repos.commits.get(user: @user, repo: @repo, sha: sha)
end

#commitsObject



63
64
65
66
67
68
69
70
71
72
# File 'lib/how_is/contributions.rb', line 63

def commits
  return @commits if defined?(@commits)

  commits = @github.repos.commits.list(user: @user, repo: @repo, since: @since_date)

  # The commits list endpoint doesn't include all commit data, e.g. stats.
  # So, we make N requests here, where N == number of commits returned,
  # and then we die a bit inside.
  @commits = commits.map { |c| commit(c.sha) }
end

#compare_urlObject



120
121
122
123
124
# File 'lib/how_is/contributions.rb', line 120

def compare_url
  since_timestamp = @since_date.to_time.to_i
  until_timestamp = @until_date.to_time.to_i
  "https://github.com/#{@user}/#{@repo}/compare/#{default_branch}@%7B#{since_timestamp}%7D...#{default_branch}@%7B#{until_timestamp}%7D"
end

#contributorsHash{String => Hash}

Returns Author information keyed by author’s email.

Returns:

  • (Hash{String => Hash})

    Author information keyed by author’s email.



57
58
59
60
61
# File 'lib/how_is/contributions.rb', line 57

def contributors
  commits.map { |api_response|
    [api_response.commit.author.email, api_response.commit.author.to_h]
  }.to_h
end

#default_branchObject

TODO: Don’t hard-code the default branch.



104
105
106
# File 'lib/how_is/contributions.rb', line 104

def default_branch
  "master"
end

#deletions_countObject



116
117
118
# File 'lib/how_is/contributions.rb', line 116

def deletions_count
  changes["stats"]["deletions"]
end

#new_contributorsHash{String => Hash] Committers keyed by GitHub login name

Returns a list of contributors that have zero commits before the @since_date.

Returns:

  • (Hash{String => Hash] Committers keyed by GitHub login name)

    Hash{String => Hash] Committers keyed by GitHub login name



45
46
47
48
49
50
51
52
53
54
# File 'lib/how_is/contributions.rb', line 45

def new_contributors
  # author: GitHub login, name or email by which to filter by commit author.
  @new_contributors ||= contributors.select do |email, _committer|
    # Returns true if +email+ never wrote a commit for +@repo+ before +@since_date+.
    @github.repos.commits.list(user: @user,
                               repo: @repo,
                               until: @since_date,
                               author: email).count.zero?
  end
end

#pretty_end_dateObject



130
131
132
# File 'lib/how_is/contributions.rb', line 130

def pretty_end_date
  @until_date.strftime("%b %d, %Y")
end

#pretty_start_dateObject



126
127
128
# File 'lib/how_is/contributions.rb', line 126

def pretty_start_date
  @since_date.strftime("%b %d, %Y")
end

#summary(start_text: nil) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/how_is/contributions.rb', line 134

def summary(start_text: nil)
  # TODO: Pulse has information about _all_ branches. Do we want that?
  #       If we do, we'd need to pass a branch name as the 'sha' parameter
  #       to /repos/:owner/:repo/commits.
  #       https://developer.github.com/v3/repos/commits/

  start_text ||= "From #{pretty_start_date} through #{pretty_end_date}"

  "#{start_text}, #{@user}/#{@repo} gained "\
    "<a href=\"#{compare_url}\">#{pluralize('new commit', commits.length)}</a>, " \
    "contributed by #{pluralize('author', contributors.length)}. There " \
    "#{(additions_count == 1) ? 'was' : 'were'} " \
    "#{pluralize('addition', additions_count)} and " \
    "#{pluralize('deletion', deletions_count)} across " \
    "#{pluralize('file', changed_files.length)}."
end