Class: HowIs::Sources::Github::Contributions

Inherits:
Object
  • Object
show all
Includes:
HowIs::Sources::GithubHelpers
Defined in:
lib/how_is/sources/github/contributions.rb

Overview

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

Usage:

c = HowIs::Contributions.new(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

Methods included from HowIs::Sources::GithubHelpers

#average_age_for, #average_date_for, #date_for, #newest_for, #num_with_label, #num_with_no_label, #obj_to_array_of_hashes, #oldest_for, #sort_iops_by_created_at

Constructor Details

#initialize(repository, end_date) ⇒ Contributions

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

Parameters:

  • repository (String)

    GitHub repository in the form of “user/repo”.

  • end_date (String)

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



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/how_is/sources/github/contributions.rb', line 28

def initialize(repository, end_date)
  @user, @repo = repository.split("/")
  @github = HowIs.github

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

  # NOTE: Use DateTime because it defaults to UTC and that's less gross
  #       than trying to get Date to use UTC.
  #
  #       Not using UTC for this results in #compare_url giving different
  #       results for different time zones, which makes it harder to test.
  #
  #       (I'm also guessing/hoping that GitHub's URLs use UTC.)
  @until_date = DateTime.strptime(end_date, "%Y-%m-%d")

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

  @commit = {}
  @stats = nil
  @changed_files = nil
end

Instance Method Details

#additions_countObject



120
121
122
# File 'lib/how_is/sources/github/contributions.rb', line 120

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

#changed_filesObject



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

def changed_files
  changes["files"]
end

#changesObject



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/how_is/sources/github/contributions.rb', line 92

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

    @changed_files = []

    commits.map do |commit|
      @stats.keys.each do |key|
        @stats[key] += commit.stats[key]
      end

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

    @changed_files = @changed_files.sort.uniq
  end

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

#commit(sha) ⇒ Object



88
89
90
# File 'lib/how_is/sources/github/contributions.rb', line 88

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

#commitsObject



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/how_is/sources/github/contributions.rb', line 75

def commits
  @commits ||= begin
    @github.repos.commits.list(user: @user,
                              repo: @repo,
                              since: @since_date).map { |c|
      # 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.
      commit(c.sha)
    }
  end
end

#compare_urlObject



128
129
130
131
132
# File 'lib/how_is/sources/github/contributions.rb', line 128

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" # rubocop:disable Metrics/LineLength
end

#contributorsHash{String => Hash}

Returns Author information keyed by author’s email.

Returns:

  • (Hash{String => Hash})

    Author information keyed by author’s email.



69
70
71
72
73
# File 'lib/how_is/sources/github/contributions.rb', line 69

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

#default_branchObject



134
135
136
137
# File 'lib/how_is/sources/github/contributions.rb', line 134

def default_branch
  @default_branch ||= @github.repos.get(user: @user,
    repo: @repo).default_branch
end

#deletions_countObject



124
125
126
# File 'lib/how_is/sources/github/contributions.rb', line 124

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

#new_contributorsHash{String => Hash}

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

Returns:

  • (Hash{String => Hash})

    Contributors keyed by email



57
58
59
60
61
62
63
64
65
66
# File 'lib/how_is/sources/github/contributions.rb', line 57

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

#to_html(start_text: nil) ⇒ Object Also known as: summary



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/how_is/sources/github/contributions.rb', line 139

def to_html(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_date(@since_date)} through #{pretty_date(@until_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