Skip to content

Commit e2b51d0

Browse files
authored
Merge branch 'master' into feat/submodules
2 parents f37830a + 325cf6f commit e2b51d0

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ unless you access private repositories.
5454

5555
For private GitHub repositories, you only need to allow read-only access to `Contents` and `Metadata` on the target repository. This could be done by setting `Read-only` access of `Permissions > Repository permissions > Contents`.
5656

57+
## Counting Contributors
58+
59+
* In GitHub repositories, the commit authors, [committers](https://stackoverflow.com/a/18754896), and [co-authors](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors) are counted as contributors. However, the plugin requires a GitHub token to fetch the list of co-authors. If co-authors exist but no token is provided, the plugin will show a warning and will only display the commit authors and committers.
60+
* In GitLab repositories, only the commit authors are counted as contributors.
61+
5762
## Config
5863

5964
- `enabled` - Disables plugin if set to `False` for e.g. local builds (default: `True`)
@@ -79,7 +84,7 @@ For private GitHub repositories, you only need to allow read-only access to `Con
7984
gitlab.com (self-hosted).
8085
- `api_version` - For GitHub and GitLab self-hosted, the API version part that needs to be appended to the URL.
8186
Defaults to v4 for GitLab, and nothing for GitHub Enterprise (you may need `v3`).
82-
- `docs_path` - the path to the documentation folder. Defaults to `docs`.
87+
- `docs_path` - the path to the documentation folder. Defaults to `docs/`.
8388
- `cache_dir` - The path which holds the authors cache file to speed up
8489
documentation builds. Defaults to `.cache/plugin/git-committers/`. The cache
8590
file is named `page-authors.json`.

mkdocs_git_committers_plugin_2/plugin.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def __init__(self):
4646
self.githuburl = "https://api.github.com"
4747
self.gitlaburl = "https://gitlab.com/api/v4"
4848
self.gitlabauthors_cache = dict()
49+
self.should_save_cache = False
4950

5051
def on_config(self, config):
5152
self.enabled = self.config['enabled']
@@ -61,12 +62,12 @@ def on_config(self, config):
6162
LOG.error("git-committers plugin: repository not specified")
6263
return config
6364
if self.config['enterprise_hostname'] and self.config['enterprise_hostname'] != '':
64-
if not self.config['api_version']:
65+
if not self.config.get('api_version'):
6566
self.githuburl = "https://" + self.config['enterprise_hostname'] + "/api"
6667
else:
6768
self.githuburl = "https://" + self.config['enterprise_hostname'] + "/api/" + self.config['api_version']
6869
if self.config['gitlab_hostname'] and self.config['gitlab_hostname'] != '':
69-
if not self.config['api_version']:
70+
if not self.config.get('api_version'):
7071
self.gitlaburl = "https://" + self.config['gitlab_hostname'] + "/api/v4"
7172
else:
7273
self.gitlaburl = "https://" + self.config['gitlab_hostname'] + "/api/" + self.config['api_version']
@@ -109,21 +110,24 @@ def get_contributors_to_file(self, path, submodule_repo=None):
109110
if r.status_code == 200:
110111
# Get login, url and avatar for each author. Ensure no duplicates.
111112
res = r.json()
113+
github_coauthors_exist = False
112114
for commit in res:
113115
if not self.config['gitlab_repository']:
114116
# GitHub
115117
if commit['author'] and commit['author']['login'] and commit['author']['login'] not in [author['login'] for author in authors]:
116118
authors.append({'login': commit['author']['login'],
117119
'name': commit['author']['login'],
118120
'url': commit['author']['html_url'],
119-
'avatar': commit['author']['avatar_url']
121+
'avatar': commit['author']['avatar_url'] if user['avatar_url'] is not None else ''
120122
})
121123
if commit['committer'] and commit['committer']['login'] and commit['committer']['login'] not in [author['login'] for author in authors]:
122124
authors.append({'login': commit['committer']['login'],
123125
'name': commit['committer']['login'],
124126
'url': commit['committer']['html_url'],
125-
'avatar': commit['committer']['avatar_url']
127+
'avatar': commit['committer']['avatar_url'] if user['avatar_url'] is not None else ''
126128
})
129+
if commit['commit'] and commit['commit']['message'] and '\nCo-authored-by:' in commit['commit']['message']:
130+
github_coauthors_exist = True
127131
else:
128132
# GitLab
129133
if commit['author_name']:
@@ -134,7 +138,7 @@ def get_contributors_to_file(self, path, submodule_repo=None):
134138
authors.append({'login': self.gitlabauthors_cache[commit['author_name']]['username'],
135139
'name': commit['author_name'],
136140
'url': self.gitlabauthors_cache[commit['author_name']]['web_url'],
137-
'avatar': self.gitlabauthors_cache[commit['author_name']]['avatar_url']
141+
'avatar': self.gitlabauthors_cache[commit['author_name']]['avatar_url'] if user['avatar_url'] is not None else ''
138142
})
139143
else:
140144
# Fetch author from GitLab API
@@ -151,11 +155,64 @@ def get_contributors_to_file(self, path, submodule_repo=None):
151155
authors.append({'login': user['username'],
152156
'name': user['name'],
153157
'url': user['web_url'],
154-
'avatar': user['avatar_url']
158+
'avatar': user['avatar_url'] if user['avatar_url'] is not None else ''
155159
})
156160
break
157161
else:
158162
LOG.error("git-committers: " + str(r.status_code) + " " + r.reason)
163+
if github_coauthors_exist:
164+
github_coauthors_count = 0
165+
# Get co-authors info through the GraphQL API, which is not available in the REST API
166+
if self.auth_header is None:
167+
LOG.warning("git-committers: Co-authors exist in commit messages but will not be added, since no GitHub token is provided. Set it under 'token' mkdocs.yml config or MKDOCS_GIT_COMMITTERS_APIKEY environment variable.")
168+
else:
169+
LOG.info("git-committers: fetching contributors for " + path + " using GraphQL API")
170+
# Query GraphQL API, and get a list of unique authors
171+
url = self.githuburl + "/graphql"
172+
query = {
173+
"query": """
174+
{
175+
repository(owner: "%s", name: "%s") {
176+
object(expression: "%s") {
177+
... on Commit {
178+
history(first: 100, path: "%s") {
179+
nodes {
180+
authors(first: 100) {
181+
nodes {
182+
user {
183+
login
184+
name
185+
url
186+
avatarUrl
187+
}
188+
}
189+
}
190+
}
191+
}
192+
}
193+
}
194+
}
195+
}
196+
""" % (self.config['repository'].split('/')[0], self.config['repository'].split('/')[1], self.branch, path)
197+
}
198+
r = requests.post(url=url, json=query, headers=self.auth_header)
199+
res = r.json()
200+
if r.status_code == 200:
201+
if res.get('data'):
202+
if res['data']['repository']['object']['history']['nodes']:
203+
for history_node in res['data']['repository']['object']['history']['nodes']:
204+
for author_node in history_node['authors']['nodes']:
205+
# If user is not None (GitHub user was deleted)
206+
if author_node['user']:
207+
if author_node['user']['login'] not in [author['login'] for author in authors]:
208+
authors.append({'login': author_node['user']['login'],
209+
'name': author_node['user']['name'],
210+
'url': author_node['user']['url'],
211+
'avatar': author_node['user']['avatarUrl']})
212+
github_coauthors_count += 1
213+
else:
214+
LOG.warning("git-committers: Error from GitHub GraphQL call: " + res['errors'][0]['message'])
215+
LOG.info(f"git-committers: added {github_coauthors_count} co-authors")
159216
return authors
160217
else:
161218
LOG.error("git-committers: error fetching contributors for " + path)
@@ -212,7 +269,9 @@ def list_contributors(self, path):
212269
LOG.info("git-committers: fetching submodule info for " + path + " from repository " + submodule_repo + " with path " + path_in_submodule)
213270
authors = self.get_contributors_to_file(path_in_submodule, submodule_repo=submodule_repo)
214271

215-
self.cache_page_authors[path] = {'last_commit_date': last_commit_date, 'authors': authors}
272+
if path not in self.cache_page_authors or self.cache_page_authors[path] != {'last_commit_date': last_commit_date, 'authors': authors}:
273+
self.should_save_cache = True
274+
self.cache_page_authors[path] = {'last_commit_date': last_commit_date, 'authors': authors}
216275

217276
return authors, last_commit_date
218277

@@ -240,6 +299,8 @@ def on_page_context(self, context, page, config, nav):
240299
return context
241300

242301
def on_post_build(self, config):
302+
if not self.should_save_cache:
303+
return
243304
LOG.info("git-committers: saving page authors cache file")
244305
json_data = json.dumps({'cache_date': datetime.now().strftime("%Y-%m-%d"), 'page_authors': self.cache_page_authors})
245306
os.makedirs(self.config['cache_dir'], exist_ok=True)

0 commit comments

Comments
 (0)