Skip to content

Commit 87d8457

Browse files
committed
New: support GitLab projects, use REST API
1 parent 7ad983d commit 87d8457

File tree

3 files changed

+118
-75
lines changed

3 files changed

+118
-75
lines changed

README.md

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# mkdocs-git-committers-plugin-2
22

33
MkDocs plugin for displaying a list of committers associated with a file in
4-
mkdocs. The plugin uses [GitHub's GraphQL
5-
API](https://docs.github.com/en/graphql) to fetch the list of contributors for
6-
each page.
4+
mkdocs. The plugin uses GitHub or GitLab API to fetch the list of contributors
5+
for each page.
6+
7+
🥳 NEW! Works with GitLab too!
78

89
For ease of use, this plugin is integrated in the [material for
910
mkdocs](https://squidfunk.github.io/mkdocs-material/) theme by [Martin
@@ -23,37 +24,52 @@ Install the plugin using pip:
2324

2425
Activate the plugin in `mkdocs.yml`:
2526

27+
For a repository hosted on GitHub:
28+
2629
```yaml
2730
plugins:
2831
- git-committers:
2932
repository: organization/repository
30-
branch: main
31-
token: !ENV ["MKDOCS_GIT_COMMITTERS_APIKEY"]
3233
```
3334
34-
If the token is not set in `mkdocs.yml` it will be read from the `MKDOCS_GIT_COMMITTERS_APIKEY` environment variable.
35+
For a repository hosted on GitLab:
3536
36-
**Change in 2.0.0: if no token is present, the plugin will NOT add provide git committers.**
37+
```yaml
38+
plugins:
39+
- git-committers:
40+
gitlab_repository: 12345678
41+
token: !ENV ["GH_TOKEN"]
42+
```
3743
38-
> **Note:** If you have no `plugins` entry in your config file yet, you'll likely also want to add the `search` plugin. MkDocs enables it by default if there is no `plugins` entry set, but now you have to enable it explicitly.
44+
For a repository hosted on GitLab, you need to provide a token so that the
45+
plugin can access the GitLab API. If the token is not set in `mkdocs.yml` it
46+
will be read from the `MKDOCS_GIT_COMMITTERS_APIKEY` environment variable.
3947

40-
More information about plugins in the [MkDocs documentation][mkdocs-plugins].
48+
For a repository hosted on GitHub, you can provide a token to increase the rate
49+
limit and go beyond the default 60 requests per hour per IP address. The plugin
50+
will make one request per mkdocs document. The token does not need any scope:
51+
uncheck everything when creating the GitHub Token at
52+
[github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new),
53+
unless you access private repositories.
4154

4255
## Config
4356

4457
- `enabled` - Disables plugin if set to `False` for e.g. local builds (default: `True`)
45-
- `repository` - The name of the repository, e.g. 'ojacques/mkdocs-git-committers-plugin-2'
58+
- `repository` - For GitHub, the name of the repository, e.g. 'ojacques/mkdocs-git-committers-plugin-2'
59+
- `gitlab_repository` - For GitLab, the project ID, e.g. '12345678'
4660
- `branch` - The name of the branch to get contributors from. Example: 'master' (default)
4761
- `token` - A github fine-grained token for GitHub GraphQL API calls (classic tokens work too). The token does not need any scope: uncheck everything when creating the GitHub Token at [github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new), unless you access private repositories.
48-
- `enterprise_hostname` - For GitHub enterprise: the enterprise hostname.
62+
- `enterprise_hostname` - For GitHub enterprise: the GitHub enterprise hostname.
63+
- `gitlab_hostname` - For GitLab: the GitLab hostname if different from gitlab.com (self-hosted).
4964
- `docs_path` - the path to the documentation folder. Defaults to `docs`.
50-
- `cache_dir` - The path which holds the authors cache file to speed up documentation builds. Defaults to `.cache/plugin/git-committers/`. The cache file is named `page-authors.json.json`.
65+
- `cache_dir` - The path which holds the authors cache file to speed up documentation builds. Defaults to `.cache/plugin/git-committers/`. The cache file is named `page-authors.json`.
5166
- `exclude` - Specify a list of page source paths (one per line) that should not have author(s) or last commit date included (excluded from processing by this plugin). Default is empty. Examples:
5267

5368
```
5469
# mkdocs.yml
5570
plugins:
5671
- git-committers:
72+
repository: organization/repository
5773
exclude:
5874
- README.md
5975
- subfolder/page.md

mkdocs_git_committers_plugin_2/plugin.py

Lines changed: 88 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ class GitCommittersPlugin(BasePlugin):
2121

2222
config_scheme = (
2323
('enterprise_hostname', config_options.Type(str, default='')),
24-
('repository', config_options.Type(str, default='')),
24+
('gitlab_hostname', config_options.Type(str, default='')),
25+
('repository', config_options.Type(str, default='')), # For GitHub: owner/repo
26+
('gitlab_repository', config_options.Type(int, default=0)), # For GitLab: project_id
2527
('branch', config_options.Type(str, default='master')),
2628
('docs_path', config_options.Type(str, default='docs/')),
2729
('enabled', config_options.Type(bool, default=True)),
@@ -38,6 +40,10 @@ def __init__(self):
3840
self.cache_page_authors = dict()
3941
self.exclude = list()
4042
self.cache_date = ''
43+
self.last_request_return_code = 0
44+
self.githuburl = "https://api.github.com"
45+
self.gitlaburl = "https://gitlab.com/api/v4"
46+
self.gitlabauthors = dict()
4147

4248
def on_config(self, config):
4349
self.enabled = self.config['enabled']
@@ -49,82 +55,101 @@ def on_config(self, config):
4955
if not self.config['token'] and 'MKDOCS_GIT_COMMITTERS_APIKEY' in os.environ:
5056
self.config['token'] = os.environ['MKDOCS_GIT_COMMITTERS_APIKEY']
5157

52-
if self.config['token'] and self.config['token'] != '':
53-
self.auth_header = {'Authorization': 'token ' + self.config['token'] }
54-
else:
55-
LOG.warning("git-committers plugin now requires a GitHub token. Set it under 'token' mkdocs.yml config or MKDOCS_GIT_COMMITTERS_APIKEY environment variable.")
56-
if not self.config['repository']:
58+
if not self.config['repository'] and not self.config['gitlab_repository']:
5759
LOG.error("git-committers plugin: repository not specified")
5860
return config
5961
if self.config['enterprise_hostname'] and self.config['enterprise_hostname'] != '':
60-
self.githuburl = "https://" + self.config['enterprise_hostname'] + "/api/graphql"
62+
self.githuburl = "https://" + self.config['enterprise_hostname'] + "/api"
63+
if self.config['gitlab_hostname'] and self.config['gitlab_hostname'] != '':
64+
self.gitlaburl = "https://" + self.config['gitlab_hostname'] + "/api/v4"
65+
# gitlab_repository must be set
66+
if not self.config['gitlab_repository']:
67+
LOG.error("git-committers plugin: gitlab_repository must be set, with the GitLab project ID")
68+
if self.config['token'] and self.config['token'] != '':
69+
if self.config['gitlab_repository']:
70+
self.auth_header = {'PRIVATE-TOKEN': self.config['token'] }
71+
else:
72+
self.auth_header = {'Authorization': 'token ' + self.config['token'] }
6173
else:
62-
self.githuburl = "https://api.github.com/graphql"
74+
self.auth_header = None
75+
if self.config['gitlab_repository']:
76+
LOG.error("git-committers plugin: GitLab API requires a token. Set it under 'token' mkdocs.yml config or MKDOCS_GIT_COMMITTERS_APIKEY environment variable.")
77+
else:
78+
LOG.warning("git-committers plugin may require a GitHub or GitLab token if you exceed the API rate limit or for private repositories. Set it under 'token' mkdocs.yml config or MKDOCS_GIT_COMMITTERS_APIKEY environment variable.")
6379
self.localrepo = Repo(".")
6480
self.branch = self.config['branch']
6581
self.excluded_pages = self.config['exclude']
6682
return config
6783

68-
# Get unique contributors for a given path using GitHub GraphQL API
69-
def get_contributors_to_path(self, path):
70-
# Query GraphQL API, and get a list of unique authors
71-
query = {
72-
"query": """
73-
{
74-
repository(owner: "%s", name: "%s") {
75-
object(expression: "%s") {
76-
... on Commit {
77-
history(first: 100, path: "%s") {
78-
nodes {
79-
author {
80-
user {
81-
login
82-
name
83-
url
84-
avatarUrl
85-
}
86-
}
87-
}
88-
}
89-
}
90-
}
91-
}
92-
}
93-
""" % (self.config['repository'].split('/')[0], self.config['repository'].split('/')[1], self.branch, path)
94-
}
84+
# Get unique contributors for a given path
85+
def get_contributors_to_file(self, path):
86+
# We already got a 401 (unauthorized) or 403 (rate limit) error, so we don't try again
87+
if self.last_request_return_code == 403 or self.last_request_return_code == 401:
88+
return []
89+
if self.config['gitlab_repository']:
90+
# REST endpoint is in the form https://gitlab.com/api/v4/projects/[project ID]/repository/commits?path=[uri-encoded-path]&ref_name=[branch]
91+
url = self.gitlaburl + "/projects/" + str(self.config['gitlab_repository']) + "/repository/commits?path=" + requests.utils.quote(path) + "&ref_name=" + self.branch
92+
else:
93+
# REST endpoint is in the form https://api.github.com/repos/[repository]/commits?path=[uri-encoded-path]&sha=[branch]&per_page=100
94+
url = self.githuburl + "/repos/" + self.config['repository'] + "/commits?path=" + requests.utils.quote(path) + "&sha=" + self.branch + "&per_page=100"
9595
authors = []
96-
if not hasattr(self, 'auth_header'):
97-
# No auth token provided: return now
98-
return None
9996
LOG.info("git-committers: fetching contributors for " + path)
100-
LOG.debug(" from " + self.githuburl)
101-
r = requests.post(url=self.githuburl, json=query, headers=self.auth_header)
102-
res = r.json()
103-
#print(res)
97+
r = requests.get(url=url, headers=self.auth_header)
98+
self.last_request_return_code = r.status_code
10499
if r.status_code == 200:
105-
if res.get('data'):
106-
if res['data']['repository']['object']['history']['nodes']:
107-
for node in res['data']['repository']['object']['history']['nodes']:
108-
# If user is not None (GitHub user was deleted)
109-
if node['author']['user']:
110-
login = node['author']['user']['login']
111-
if login not in [author['login'] for author in authors]:
112-
authors.append({'login': node['author']['user']['login'],
113-
'name': node['author']['user']['name'],
114-
'url': node['author']['user']['url'],
115-
'avatar': node['author']['user']['avatarUrl']})
116-
return authors
117-
else:
118-
return []
100+
# Get login, url and avatar for each author. Ensure no duplicates.
101+
res = r.json()
102+
for commit in res:
103+
if not self.config['gitlab_repository']:
104+
# GitHub
105+
if commit['author'] and commit['author']['login'] and commit['author']['login'] not in [author['login'] for author in authors]:
106+
authors.append({'login': commit['author']['login'],
107+
'name': commit['author']['login'],
108+
'url': commit['author']['html_url'],
109+
'avatar': commit['author']['avatar_url']})
119110
else:
120-
LOG.warning("git-committers: Error from GitHub GraphQL call: " + res['errors'][0]['message'])
121-
return []
111+
# GitLab
112+
if commit['author_name']:
113+
# If author is not already in the list of authors
114+
if commit['author_name'] not in [author['name'] for author in authors]:
115+
# Look for GitLab author in our cache self.gitlabauthors. If not found fetch it from GitLab API and save it in cache.
116+
if commit['author_name'] in self.gitlabauthors:
117+
authors.append({'login': self.gitlabauthors[commit['author_name']]['username'],
118+
'name': commit['author_name'],
119+
'url': self.gitlabauthors[commit['author_name']]['web_url'],
120+
'avatar': self.gitlabauthors[commit['author_name']]['avatar_url']})
121+
else:
122+
# Fetch author from GitLab API
123+
url = self.gitlaburl + "/users?search=" + requests.utils.quote(commit['author_name'])
124+
r = requests.get(url=url, headers=self.auth_header)
125+
if r.status_code == 200:
126+
res = r.json()
127+
if len(res) > 0:
128+
# Go through all users until we find the one with the same name
129+
for user in res:
130+
if user['name'] == commit['author_name']:
131+
# Save it in cache
132+
self.gitlabauthors[commit['author_name']] = user
133+
authors.append({'login': user['username'],
134+
'name': user['name'],
135+
'url': user['web_url'],
136+
'avatar': user['avatar_url']})
137+
break
138+
else:
139+
LOG.error("git-committers: " + str(r.status_code) + " " + r.reason)
140+
return authors
122141
else:
123-
return []
142+
LOG.error("git-committers: error fetching contributors for " + path)
143+
if r.status_code == 403 or r.status_code == 401:
144+
LOG.error("git-committers: " + str(r.status_code) + " " + r.reason + " - You may have exceeded the API rate limit or need to be authorized. You can set a token under 'token' mkdocs.yml config or MKDOCS_GIT_COMMITTERS_APIKEY environment variable.")
145+
else:
146+
LOG.error("git-committers: " + str(r.status_code) + " " + r.reason)
147+
return []
124148
return []
125149

126150
def list_contributors(self, path):
127151
if exclude(path.lstrip(self.config['docs_path']), self.excluded_pages):
152+
LOG.warning("git-committers: " + path + " is excluded")
128153
return None, None
129154

130155
last_commit_date = ""
@@ -142,10 +167,12 @@ def list_contributors(self, path):
142167
# Use the cache if present if cache date is newer than last commit date
143168
if path in self.cache_page_authors:
144169
if self.cache_date and time.strptime(last_commit_date, "%Y-%m-%d") < time.strptime(self.cache_date, "%Y-%m-%d"):
145-
return self.cache_page_authors[path]['authors'], self.cache_page_authors[path]['last_commit_date']
170+
# If page_autors in cache is not empty, return it
171+
if self.cache_page_authors[path]['authors']:
172+
return self.cache_page_authors[path]['authors'], self.cache_page_authors[path]['last_commit_date']
146173

147174
authors=[]
148-
authors = self.get_contributors_to_path(path)
175+
authors = self.get_contributors_to_file(path)
149176

150177
self.cache_page_authors[path] = {'last_commit_date': last_commit_date, 'authors': authors}
151178

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
setup(
1111
name="mkdocs-git-committers-plugin-2",
12-
version="2.0.1",
13-
description="An MkDocs plugin to create a list of contributors on the page. The git-committers plugin will seed the template context with a list of github committers and other useful GIT info such as last modified date",
12+
version="2.1.0",
13+
description="An MkDocs plugin to create a list of contributors on the page. The git-committers plugin will seed the template context with a list of GitHub or GitLab committers and other useful GIT info such as last modified date",
1414
long_description=long_description,
1515
long_description_content_type="text/markdown",
1616
keywords="mkdocs, plugin, github, committers",

0 commit comments

Comments
 (0)