cgitize 7.0.0__tar.gz → 8.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {cgitize-7.0.0 → cgitize-8.0.1}/.github/dependabot.yml +1 -4
  2. {cgitize-7.0.0 → cgitize-8.0.1}/.github/workflows/ci.yml +13 -12
  3. {cgitize-7.0.0 → cgitize-8.0.1}/.github/workflows/maintenance.yml +6 -8
  4. {cgitize-7.0.0 → cgitize-8.0.1}/DEVELOPMENT.md +3 -3
  5. {cgitize-7.0.0 → cgitize-8.0.1}/PKG-INFO +1 -1
  6. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/bitbucket.py +2 -2
  7. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/config.py +5 -10
  8. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/repo.py +25 -7
  9. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/utils.py +22 -8
  10. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize.egg-info/PKG-INFO +1 -1
  11. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize.egg-info/SOURCES.txt +2 -0
  12. cgitize-8.0.1/cgitize.egg-info/scm_file_list.json +64 -0
  13. cgitize-8.0.1/cgitize.egg-info/scm_version.json +8 -0
  14. {cgitize-7.0.0 → cgitize-8.0.1}/docker/README.md +2 -2
  15. {cgitize-7.0.0 → cgitize-8.0.1}/docs/api.md +6 -4
  16. {cgitize-7.0.0 → cgitize-8.0.1}/docs/auth.md +3 -5
  17. {cgitize-7.0.0 → cgitize-8.0.1}/examples/cgitize.toml +16 -16
  18. {cgitize-7.0.0 → cgitize-8.0.1}/requirements.txt +6 -6
  19. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/git_server/Dockerfile +1 -1
  20. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/example/test.sh +48 -3
  21. {cgitize-7.0.0 → cgitize-8.0.1}/test/set_credentials.sh +3 -1
  22. {cgitize-7.0.0 → cgitize-8.0.1}/test/unit/test_bitbucket.py +16 -1
  23. {cgitize-7.0.0 → cgitize-8.0.1}/.dockerignore +0 -0
  24. {cgitize-7.0.0 → cgitize-8.0.1}/.gitattributes +0 -0
  25. {cgitize-7.0.0 → cgitize-8.0.1}/.gitignore +0 -0
  26. {cgitize-7.0.0 → cgitize-8.0.1}/.pylintrc +0 -0
  27. {cgitize-7.0.0 → cgitize-8.0.1}/.python-version +0 -0
  28. {cgitize-7.0.0 → cgitize-8.0.1}/LICENSE.txt +0 -0
  29. {cgitize-7.0.0 → cgitize-8.0.1}/Makefile +0 -0
  30. {cgitize-7.0.0 → cgitize-8.0.1}/README.md +0 -0
  31. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/__init__.py +0 -0
  32. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/cgit.py +0 -0
  33. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/git.py +0 -0
  34. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/github.py +0 -0
  35. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/gitlab.py +0 -0
  36. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/header.py +0 -0
  37. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/main.py +0 -0
  38. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize/version.py +0 -0
  39. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize.egg-info/dependency_links.txt +0 -0
  40. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize.egg-info/entry_points.txt +0 -0
  41. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize.egg-info/requires.txt +0 -0
  42. {cgitize-7.0.0 → cgitize-8.0.1}/cgitize.egg-info/top_level.txt +0 -0
  43. {cgitize-7.0.0 → cgitize-8.0.1}/docker/Dockerfile +0 -0
  44. {cgitize-7.0.0 → cgitize-8.0.1}/docker/cgitize.sh +0 -0
  45. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/Dockerfile +0 -0
  46. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/cmd.sh +0 -0
  47. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/etc/cgit/common +0 -0
  48. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/etc/cgitrc +0 -0
  49. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/etc/nginx/conf.d/default.conf +0 -0
  50. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/usr/lib/cgit/filters/about-formatting.sh +0 -0
  51. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/usr/lib/cgit/filters/html-converters/.gitattributes +0 -0
  52. {cgitize-7.0.0 → cgitize-8.0.1}/docker/frontend/usr/lib/cgit/filters/html-converters/md2html +0 -0
  53. {cgitize-7.0.0 → cgitize-8.0.1}/docker/get_output_dir.py +0 -0
  54. {cgitize-7.0.0 → cgitize-8.0.1}/docker/in_cron.sh +0 -0
  55. {cgitize-7.0.0 → cgitize-8.0.1}/docker-compose.yml +0 -0
  56. {cgitize-7.0.0 → cgitize-8.0.1}/examples/docker/cgitize.toml +0 -0
  57. {cgitize-7.0.0 → cgitize-8.0.1}/prelude.mk +0 -0
  58. {cgitize-7.0.0 → cgitize-8.0.1}/pyproject.toml +0 -0
  59. {cgitize-7.0.0 → cgitize-8.0.1}/setup.cfg +0 -0
  60. {cgitize-7.0.0 → cgitize-8.0.1}/test/__init__.py +0 -0
  61. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/.dockerignore +0 -0
  62. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/cgitize/.gitignore +0 -0
  63. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/cgitize/etc/cgitize.toml +0 -0
  64. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/docker-compose.yml +0 -0
  65. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/git_server/setup_repo.sh +0 -0
  66. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/docker/test.sh +0 -0
  67. {cgitize-7.0.0 → cgitize-8.0.1}/test/integration/local/test.sh +0 -0
  68. {cgitize-7.0.0 → cgitize-8.0.1}/test/unit/__init__.py +0 -0
  69. {cgitize-7.0.0 → cgitize-8.0.1}/test/unit/test_github.py +0 -0
  70. {cgitize-7.0.0 → cgitize-8.0.1}/test/unit/test_gitlab.py +0 -0
@@ -14,7 +14,4 @@ updates:
14
14
  directory: /
15
15
  schedule:
16
16
  interval: weekly
17
- - package-ecosystem: pip
18
- directory: /
19
- schedule:
20
- interval: weekly
17
+ # No pip, because it's automated in the maintenance workflow anyway.
@@ -10,9 +10,10 @@ jobs:
10
10
  lint:
11
11
  runs-on: ubuntu-latest
12
12
  name: Linting
13
+ continue-on-error: "${{ github.ref != 'refs/heads/master' }}"
13
14
  steps:
14
15
  - name: Checkout
15
- uses: actions/checkout@v6
16
+ uses: actions/checkout@v7
16
17
  - name: Set up Python
17
18
  uses: actions/setup-python@v6
18
19
  with:
@@ -27,15 +28,15 @@ jobs:
27
28
  python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
28
29
  name: 'Test / ${{ matrix.python-version }}'
29
30
  env:
30
- CGITIZE_GITHUB_USERNAME: cgitize-test
31
+ CGITIZE_GITHUB_USERNAME: '${{ secrets.CGITIZE_GITHUB_USERNAME }}'
31
32
  CGITIZE_GITHUB_TOKEN: '${{ secrets.CGITIZE_GITHUB_TOKEN }}'
32
- CGITIZE_BITBUCKET_USERNAME: cgitize-test
33
+ CGITIZE_BITBUCKET_EMAIL: '${{ secrets.CGITIZE_BITBUCKET_EMAIL }}'
33
34
  CGITIZE_BITBUCKET_TOKEN: '${{ secrets.CGITIZE_BITBUCKET_TOKEN }}'
34
- CGITIZE_GITLAB_USERNAME: cgitize-test
35
+ CGITIZE_GITLAB_USERNAME: '${{ secrets.CGITIZE_GITLAB_USERNAME }}'
35
36
  CGITIZE_GITLAB_TOKEN: '${{ secrets.CGITIZE_GITLAB_TOKEN }}'
36
37
  steps:
37
38
  - name: Checkout
38
- uses: actions/checkout@v6
39
+ uses: actions/checkout@v7
39
40
  with:
40
41
  fetch-depth: 0
41
42
  - name: Set up Python
@@ -55,7 +56,7 @@ jobs:
55
56
  name: 'Test / Docker'
56
57
  steps:
57
58
  - name: Checkout
58
- uses: actions/checkout@v6
59
+ uses: actions/checkout@v7
59
60
  - name: Integration test (Docker)
60
61
  run: sudo make test/docker
61
62
 
@@ -64,15 +65,15 @@ jobs:
64
65
  runs-on: ubuntu-latest
65
66
  name: 'Test / example config'
66
67
  env:
67
- CGITIZE_GITHUB_USERNAME: cgitize-test
68
+ CGITIZE_GITHUB_USERNAME: '${{ secrets.CGITIZE_GITHUB_USERNAME }}'
68
69
  CGITIZE_GITHUB_TOKEN: '${{ secrets.CGITIZE_GITHUB_TOKEN }}'
69
- CGITIZE_BITBUCKET_USERNAME: cgitize-test
70
+ CGITIZE_BITBUCKET_EMAIL: '${{ secrets.CGITIZE_BITBUCKET_EMAIL }}'
70
71
  CGITIZE_BITBUCKET_TOKEN: '${{ secrets.CGITIZE_BITBUCKET_TOKEN }}'
71
- CGITIZE_GITLAB_USERNAME: cgitize-test
72
+ CGITIZE_GITLAB_USERNAME: '${{ secrets.CGITIZE_GITLAB_USERNAME }}'
72
73
  CGITIZE_GITLAB_TOKEN: '${{ secrets.CGITIZE_GITLAB_TOKEN }}'
73
74
  steps:
74
75
  - name: Checkout
75
- uses: actions/checkout@v6
76
+ uses: actions/checkout@v7
76
77
  - name: Set up Python
77
78
  uses: actions/setup-python@v6
78
79
  with:
@@ -98,7 +99,7 @@ jobs:
98
99
  name: 'Publish / Docker Hub / ${{ matrix.service }}'
99
100
  steps:
100
101
  - name: Checkout
101
- uses: actions/checkout@v6
102
+ uses: actions/checkout@v7
102
103
  # https://github.com/docker/metadata-action#semver
103
104
  - id: meta
104
105
  name: Docker Hub metadata
@@ -153,7 +154,7 @@ jobs:
153
154
  name: 'Publish / PyPI'
154
155
  steps:
155
156
  - name: Checkout
156
- uses: actions/checkout@v6
157
+ uses: actions/checkout@v7
157
158
  with:
158
159
  fetch-depth: 0
159
160
  - name: Set up Python
@@ -12,12 +12,11 @@ jobs:
12
12
  name: Maintenance
13
13
  steps:
14
14
  - name: Checkout
15
- uses: actions/checkout@v6
15
+ uses: actions/checkout@v7
16
16
  with:
17
17
  # I think that by default, pushes don't trigger other workflows, and
18
- # you need a separate token for that: https://github.com/orgs/community/discussions/37103
19
- #
20
- # Re-using the Jekyll projects token here BTW:
18
+ # you need a separate token for that:
19
+ # https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow#triggering-a-workflow-from-a-workflow
21
20
  token: '${{ secrets.GH_TOKEN }}'
22
21
  - name: Set up ssh-agent
23
22
  uses: webfactory/ssh-agent@v0.10.0
@@ -27,11 +26,10 @@ jobs:
27
26
  uses: actions/setup-python@v6
28
27
  with:
29
28
  python-version-file: .python-version
30
- - name: Run maintenance
31
- id: maintenance
29
+ - name: Configure git
32
30
  run: |
33
31
  # See https://github.com/actions/checkout#push-a-commit-using-the-built-in-token
34
32
  git config user.name "github-actions[bot]"
35
33
  git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
36
-
37
- make maintenance
34
+ - name: Run maintenance
35
+ run: make maintenance
@@ -18,11 +18,11 @@ test.
18
18
  You need to set the following environment variables for the authenticated
19
19
  access tests to work:
20
20
 
21
- * `CGITIZE_BITBUCKET_USERNAME`: `cgitize-test`
21
+ * `CGITIZE_BITBUCKET_EMAIL`
22
22
  * `CGITIZE_BITBUCKET_TOKEN`
23
- * `CGITIZE_GITHUB_USERNAME`: `cgitize-test`
23
+ * `CGITIZE_GITHUB_USERNAME`
24
24
  * `CGITIZE_GITHUB_TOKEN`
25
- * `CGITIZE_GITLAB_USERNAME`: `cgitize-test`
25
+ * `CGITIZE_GITLAB_USERNAME`
26
26
  * `CGITIZE_GITLAB_TOKEN`
27
27
 
28
28
  You can run `. test/set_credentials.sh`; it'll ask you to fill in the secrets.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgitize
3
- Version: 7.0.0
3
+ Version: 8.0.1
4
4
  Summary: Self-host your repositories using cgit
5
5
  Author-email: Egor Tensin <egor@tensin.name>
6
6
  License-Expression: MIT
@@ -12,8 +12,8 @@ from cgitize.repo import Repo, Visibility
12
12
 
13
13
 
14
14
  class Bitbucket:
15
- def __init__(self, username=None, password=None):
16
- self._impl = Cloud(username=username, password=password, cloud=True)
15
+ def __init__(self, email=None, token=None):
16
+ self._impl = Cloud(username=email, password=token, cloud=True)
17
17
 
18
18
  def get_repo(self, repo):
19
19
  try:
@@ -5,6 +5,7 @@
5
5
 
6
6
  from abc import ABC, abstractmethod
7
7
  import os
8
+ from urllib.parse import quote
8
9
 
9
10
  import tomli
10
11
 
@@ -110,7 +111,7 @@ class GitHubSection(ServiceSection):
110
111
 
111
112
  @property
112
113
  def url_auth(self):
113
- return self.token
114
+ return (self.token, None)
114
115
 
115
116
  def _enum_org_repositories(self, cfg, api, org):
116
117
  for repo in api.get_org_repos(org):
@@ -135,12 +136,6 @@ class GitHubSection(ServiceSection):
135
136
  return GitHub(username, token)
136
137
 
137
138
 
138
- def two_part_url_auth(username, password):
139
- if username is None or password is None:
140
- return None
141
- return f'{username}:{password}'
142
-
143
-
144
139
  class BitbucketSection(ServiceSection):
145
140
  @property
146
141
  def token(self):
@@ -148,11 +143,11 @@ class BitbucketSection(ServiceSection):
148
143
 
149
144
  @property
150
145
  def username(self):
151
- return self._get_config_or_env('username', 'CGITIZE_BITBUCKET_USERNAME')
146
+ return self._get_config_or_env('email', 'CGITIZE_BITBUCKET_EMAIL')
152
147
 
153
148
  @property
154
149
  def url_auth(self):
155
- return two_part_url_auth(self.username, self.token)
150
+ return (self.username, self.token)
156
151
 
157
152
  def connect_to_service(self):
158
153
  username = self.username
@@ -175,7 +170,7 @@ class GitLabSection(ServiceSection):
175
170
 
176
171
  @property
177
172
  def url_auth(self):
178
- return two_part_url_auth(self.username, self.token)
173
+ return (self.username, self.token)
179
174
 
180
175
  def connect_to_service(self):
181
176
  username = self.username
@@ -87,8 +87,12 @@ class Repo:
87
87
 
88
88
  https_url = src.clone_url
89
89
  ssh_url = src.ssh_url
90
- clone_url = ssh_url if config.main.clone_via_ssh else https_url
91
- url_auth = None if config.main.clone_via_ssh else config.github.url_auth
90
+
91
+ clone_url = ssh_url
92
+ url_auth = None
93
+ if not config.main.clone_via_ssh:
94
+ clone_url = https_url
95
+ url_auth = config.github.url_auth
92
96
 
93
97
  return Repo(
94
98
  name,
@@ -113,7 +117,7 @@ class Repo:
113
117
  if len(https_urls) != 1:
114
118
  raise RuntimeError(f"no https:// clone URL for repository '{name}'?!")
115
119
  # Bitbucket leaves the username in the URL... Sigh.
116
- https_url = url_remove_auth(https_urls[0]['href'])
120
+ api_auth, https_url = url_remove_auth(https_urls[0]['href'])
117
121
 
118
122
  ssh_urls = [
119
123
  link for link in src.data['links']['clone'] if link['name'] == 'ssh'
@@ -122,8 +126,18 @@ class Repo:
122
126
  raise RuntimeError(f"no ssh:// clone URL for repository '{name}'?!")
123
127
  ssh_url = ssh_urls[0]['href']
124
128
 
125
- clone_url = ssh_url if config.main.clone_via_ssh else https_url
126
- url_auth = None if config.main.clone_via_ssh else config.bitbucket.url_auth
129
+ clone_url = ssh_url
130
+ url_auth = None
131
+ if not config.main.clone_via_ssh:
132
+ clone_url = https_url
133
+ url_auth = config.bitbucket.url_auth
134
+ # Bitbucket, since deprecating app passwords, now requires that
135
+ # users authenticate with their Atlassian email & API token.
136
+ # Unfortunately (sigh...), if you want to clone a repo via HTTPS,
137
+ # you can't use the very same email + token combo in the URL, you
138
+ # have to use your username (double sigh...). Luckily, for some
139
+ # reason, Bitbucket leaves the relevant username in the API response.
140
+ url_auth = api_auth[0], url_auth[1]
127
141
 
128
142
  return Repo(
129
143
  name,
@@ -144,8 +158,12 @@ class Repo:
144
158
 
145
159
  https_url = src.http_url_to_repo
146
160
  ssh_url = src.ssh_url_to_repo
147
- clone_url = ssh_url if config.main.clone_via_ssh else https_url
148
- url_auth = None if config.main.clone_via_ssh else config.gitlab.url_auth
161
+
162
+ clone_url = ssh_url
163
+ url_auth = None
164
+ if not config.main.clone_via_ssh:
165
+ clone_url = https_url
166
+ url_auth = config.gitlab.url_auth
149
167
 
150
168
  return Repo(
151
169
  name,
@@ -9,25 +9,35 @@ import os
9
9
  import stat
10
10
  import subprocess
11
11
  import sys
12
- from urllib.parse import urlsplit, urlunsplit
12
+ from urllib.parse import quote, urlsplit, urlunsplit
13
13
 
14
14
 
15
15
  @contextmanager
16
16
  def setup_logging(verbose=False):
17
+ level_names = {
18
+ logging.DEBUG: 'DBG',
19
+ logging.INFO: 'INFO',
20
+ logging.WARNING: 'WARN',
21
+ logging.ERROR: 'ERR',
22
+ logging.CRITICAL: 'CRIT',
23
+ }
24
+ for lvl, name in level_names.items():
25
+ logging.addLevelName(lvl, name)
26
+
17
27
  level = logging.DEBUG if verbose else logging.INFO
18
28
  logging.basicConfig(
19
29
  level=level,
20
30
  datefmt='%Y-%m-%d %H:%M:%S%z',
21
- format='%(asctime)s | %(levelname)s | %(message)s',
31
+ format='%(asctime)s | %(levelname)4s | %(message)s',
22
32
  # Log to stdout, because that's where subprocess's output goes (so that
23
- # the don't get interleaved).
33
+ # they don't get interleaved).
24
34
  stream=sys.stdout,
25
35
  )
26
36
  try:
27
37
  yield
28
38
  except Exception as e:
29
39
  logging.exception(e)
30
- raise
40
+ sys.exit(1)
31
41
 
32
42
 
33
43
  def run(*args, capture_output=False, **kwargs):
@@ -97,11 +107,12 @@ def protected_file(path):
97
107
  os.unlink(path)
98
108
 
99
109
 
100
- def url_replace_auth(url, username, password=None):
110
+ def url_replace_auth(url, auth):
111
+ username, password = auth
101
112
  parts = urlsplit(url)
102
- netloc = username
113
+ netloc = quote(username)
103
114
  if password is not None:
104
- netloc += f':{password}'
115
+ netloc += f':{quote(password)}'
105
116
  netloc += f'@{parts.hostname}'
106
117
  if parts.port is not None:
107
118
  netloc += f':{parts.port}'
@@ -111,6 +122,9 @@ def url_replace_auth(url, username, password=None):
111
122
 
112
123
  def url_remove_auth(url):
113
124
  parts = urlsplit(url)
125
+ auth = parts.username, parts.password
114
126
  netloc = parts.hostname
127
+ if parts.port is not None:
128
+ netloc += f':{parts.port}'
115
129
  parts = parts._replace(netloc=netloc)
116
- return urlunsplit(parts)
130
+ return auth, urlunsplit(parts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgitize
3
- Version: 7.0.0
3
+ Version: 8.0.1
4
4
  Summary: Self-host your repositories using cgit
5
5
  Author-email: Egor Tensin <egor@tensin.name>
6
6
  License-Expression: MIT
@@ -31,6 +31,8 @@ cgitize.egg-info/SOURCES.txt
31
31
  cgitize.egg-info/dependency_links.txt
32
32
  cgitize.egg-info/entry_points.txt
33
33
  cgitize.egg-info/requires.txt
34
+ cgitize.egg-info/scm_file_list.json
35
+ cgitize.egg-info/scm_version.json
34
36
  cgitize.egg-info/top_level.txt
35
37
  docker/Dockerfile
36
38
  docker/README.md
@@ -0,0 +1,64 @@
1
+ {
2
+ "files": [
3
+ "README.md",
4
+ "Makefile",
5
+ ".python-version",
6
+ "LICENSE.txt",
7
+ "prelude.mk",
8
+ "pyproject.toml",
9
+ "requirements.txt",
10
+ ".dockerignore",
11
+ ".gitattributes",
12
+ ".pylintrc",
13
+ ".gitignore",
14
+ "docker-compose.yml",
15
+ "DEVELOPMENT.md",
16
+ "docs/auth.md",
17
+ "docs/api.md",
18
+ "cgitize/bitbucket.py",
19
+ "cgitize/gitlab.py",
20
+ "cgitize/__init__.py",
21
+ "cgitize/git.py",
22
+ "cgitize/version.py",
23
+ "cgitize/config.py",
24
+ "cgitize/utils.py",
25
+ "cgitize/header.py",
26
+ "cgitize/repo.py",
27
+ "cgitize/cgit.py",
28
+ "cgitize/main.py",
29
+ "cgitize/github.py",
30
+ "test/__init__.py",
31
+ "test/set_credentials.sh",
32
+ "test/integration/example/test.sh",
33
+ "test/integration/docker/.dockerignore",
34
+ "test/integration/docker/test.sh",
35
+ "test/integration/docker/docker-compose.yml",
36
+ "test/integration/docker/cgitize/.gitignore",
37
+ "test/integration/docker/cgitize/etc/cgitize.toml",
38
+ "test/integration/docker/git_server/Dockerfile",
39
+ "test/integration/docker/git_server/setup_repo.sh",
40
+ "test/integration/local/test.sh",
41
+ "test/unit/__init__.py",
42
+ "test/unit/test_gitlab.py",
43
+ "test/unit/test_bitbucket.py",
44
+ "test/unit/test_github.py",
45
+ "docker/README.md",
46
+ "docker/get_output_dir.py",
47
+ "docker/Dockerfile",
48
+ "docker/cgitize.sh",
49
+ "docker/in_cron.sh",
50
+ "docker/frontend/Dockerfile",
51
+ "docker/frontend/cmd.sh",
52
+ "docker/frontend/usr/lib/cgit/filters/about-formatting.sh",
53
+ "docker/frontend/usr/lib/cgit/filters/html-converters/md2html",
54
+ "docker/frontend/usr/lib/cgit/filters/html-converters/.gitattributes",
55
+ "docker/frontend/etc/cgitrc",
56
+ "docker/frontend/etc/nginx/conf.d/default.conf",
57
+ "docker/frontend/etc/cgit/common",
58
+ "examples/cgitize.toml",
59
+ "examples/docker/cgitize.toml",
60
+ ".github/dependabot.yml",
61
+ ".github/workflows/maintenance.yml",
62
+ ".github/workflows/ci.yml"
63
+ ]
64
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "8.0.1",
3
+ "distance": 0,
4
+ "node": "g05044361ec94a24dc370196e5234b0292cfefe96",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-06-23"
8
+ }
@@ -48,13 +48,13 @@ Here's an example docker-compose.yml file:
48
48
  SCHEDULE: '0 */3 * * *'
49
49
  # Set CGITIZE_{GITHUB,BITBUCKET,GITLAB}_{USERNAME,TOKEN} variables
50
50
  # here or in the config file.
51
- image: egortensin/cgitize:6
51
+ image: egortensin/cgitize:8
52
52
  restart: unless-stopped
53
53
  volumes:
54
54
  - ./example.toml:/etc/cgitize/cgitize.toml:ro
55
55
  - /srv/volumes/cgitize:/mnt/cgitize
56
56
  frontend:
57
- image: egortensin/cgitize-frontend:6
57
+ image: egortensin/cgitize-frontend:8
58
58
  ports:
59
59
  - '127.0.0.1:80:80'
60
60
  restart: unless-stopped
@@ -4,10 +4,9 @@ unfortunately, peculiar in their own way.
4
4
  Tokens
5
5
  ------
6
6
 
7
- All of the forges support access tokens, of course (although they're called
8
- "app passwords" on Bitbucket). They can be used to do `git clone https://`
9
- with authentication info in the URL. For GitHub, it's enough to just specify
10
- the token:
7
+ All of the forges support access tokens, of course. They can be used to do
8
+ `git clone https://` with authentication info in the URL. For GitHub, it's
9
+ enough to just specify the token:
11
10
 
12
11
  git clone https://TOKEN@github.com/USER/REPO.git
13
12
 
@@ -16,6 +15,9 @@ token in the URL:
16
15
 
17
16
  git clone https://USER:TOKEN@bitbucket.org/USER/REPO.git
18
17
 
18
+ (For Bitbucket, since they deprecated their "app passwords", you need to
19
+ specify not the username, but rather the Atlassian account email.)
20
+
19
21
  Terminology
20
22
  -----------
21
23
 
@@ -22,11 +22,9 @@ When using HTTPS, there're multiple options to access the private repositories.
22
22
 
23
23
  2. Use OAuth. I thought this was too complicated to implement quickly.
24
24
 
25
- 3. Use per-application passwords. This option is pretty cool. Both GitHub (in
26
- the form of "personal access tokens") and Bitbucket (calls them "app
27
- passwords") support creating one-purpose passwords that are supposed to be
28
- used by a single app only. They allow to bypass the 2FA also. Let's do
29
- this!
25
+ 3. Use application access tokens. This option is pretty cool. Both GitHub
26
+ and Bitbucket support creating read-only tokens. They allow to bypass the
27
+ 2FA also. Let's do this!
30
28
 
31
29
  Passing the access tokens to Git is tricky though. First, simply including
32
30
  them in the clone URL would be incredibly stupid, since that URL is going to be
@@ -7,8 +7,8 @@ output_dir = "/tmp/cgitize"
7
7
  # repository's directory path.
8
8
  clone_url = "https://yourhost.com/git/{repo}"
9
9
 
10
- # Clones via SSH by default.
11
- clone_via_ssh = false
10
+ # Clones via SSH by default (instead of Git-over-HTTPS).
11
+ clone_via_ssh = true
12
12
 
13
13
  # Unless this is specified, cgit is going to derive the owner from the
14
14
  # repository's directory ownership.
@@ -19,8 +19,10 @@ owner = "Your Name"
19
19
 
20
20
  [github]
21
21
  # If some of the repositories hosted on GitHub are private (or if you hit the
22
- # request rate limit), use "access tokens". Set in the config or using the
23
- # CGITIZE_GITHUB_TOKEN environment variable.
22
+ # request rate limit), use a personal access token. Set in the config or using
23
+ # the CGITIZE_GITHUB_TOKEN environment variable.
24
+ # Required scopes:
25
+ # * repo
24
26
  #token = "XXX"
25
27
  # Set the username that owns the token here or using the environment variable
26
28
  # CGITIZE_GITHUB_USERNAME.
@@ -56,21 +58,20 @@ dir = "python"
56
58
  # Bitbucket
57
59
  # =========
58
60
 
59
- # Bitbucket settings are similar to those for GitHub.
60
-
61
61
  [bitbucket]
62
- # Access tokens are called "app passwords" on Bitbucket.
63
- # The environment variable is CGITIZE_BITBUCKET_TOKEN.
62
+ # Atlassian API token (environment variable CGITIZE_BITBUCKET_TOKEN).
63
+ # Required scopes:
64
+ # * read:workspace:bitbucket
65
+ # * read:repository:bitbucket
64
66
  #token = "XXX"
65
- # The environment variable is CGITIZE_BITBUCKET_USERNAME.
66
- #username = "your-username"
67
+ # Atlassian account email address (environment variable CGITIZE_BITBUCKET_EMAIL).
68
+ #email = "your-email"
67
69
 
68
70
  [bitbucket.users.me]
69
71
  name = "cgitize-test-workspace"
70
72
  dir = "bitbucket-dir"
71
73
  #skip = ["repo1", "repo2"]
72
74
 
73
- # The defaults.
74
75
  public = true
75
76
  private = false
76
77
 
@@ -84,12 +85,12 @@ id = "berkeleylab/upc-runtime"
84
85
  # GitLab
85
86
  # ======
86
87
 
87
- # GitLab settings are similar to those for GitHub.
88
-
89
88
  [gitlab]
90
- # The environment variable is CGITIZE_GITLAB_TOKEN.
89
+ # Personal access token (environment variable CGITIZE_GITLAB_TOKEN).
90
+ # Required scopes (for legacy tokens):
91
+ # * read_api
91
92
  #token = "XXX"
92
- # The environment variable is CGITIZE_GITLAB_USERNAME.
93
+ # Gitlab username (environment variable CGITIZE_GITLAB_USERNAME).
93
94
  #username = "your-username"
94
95
 
95
96
  [gitlab.users.me]
@@ -97,7 +98,6 @@ name = "cgitize-test"
97
98
  dir = "gitlab-dir"
98
99
  #skip = ["repo1", "repo2"]
99
100
 
100
- # The defaults.
101
101
  public = true
102
102
  private = false
103
103
 
@@ -1,11 +1,11 @@
1
1
  atlassian-python-api==4.0.7
2
- beautifulsoup4==4.14.3
3
- certifi==2026.5.20
2
+ beautifulsoup4==4.15.0
3
+ certifi==2026.6.17
4
4
  cffi==2.0.0
5
5
  charset-normalizer==3.4.7
6
- cryptography==48.0.0
6
+ cryptography==49.0.0
7
7
  Deprecated==1.3.1
8
- idna==3.16
8
+ idna==3.18
9
9
  jmespath==1.1.0
10
10
  oauthlib==3.3.1
11
11
  pycparser==3.0
@@ -16,8 +16,8 @@ python-gitlab==6.5.0
16
16
  requests==2.34.2
17
17
  requests-oauthlib==2.0.0
18
18
  requests-toolbelt==1.0.0
19
- soupsieve==2.8.3
19
+ soupsieve==2.8.4
20
20
  tomli==2.4.1
21
21
  typing_extensions==4.15.0
22
22
  urllib3==2.7.0
23
- wrapt==2.2.0
23
+ wrapt==2.2.1
@@ -1,4 +1,4 @@
1
- FROM alpine:3.23
1
+ FROM alpine:3.24
2
2
 
3
3
  RUN apk add -q --no-cache bash git openssh-server tini && \
4
4
  echo 'root:root' | chpasswd && \
@@ -151,7 +151,48 @@ test_ssh() {
151
151
  goauthentik-dir/authentik \
152
152
  github-dir/public \
153
153
  bitbucket-dir/public \
154
+ gitlab-dir/public
155
+
156
+ verify_no_repos \
157
+ github-dir/private \
158
+ bitbucket-dir/private \
159
+ gitlab-dir/private
160
+
161
+ verify_origin lens 'git@github.com:ekmett/lens.git'
162
+ verify_origin chromiumembedded/cef 'git@bitbucket.org:chromiumembedded/cef.git'
163
+ verify_origin graphviz 'git@gitlab.com:graphviz/graphviz.git'
164
+ verify_origin goauthentik-dir/authentik 'git@github.com:goauthentik/authentik.git'
165
+
166
+ verify_origin github-dir/public 'git@github.com:cgitize-test/public.git'
167
+ verify_origin bitbucket-dir/public 'git@bitbucket.org:cgitize-test-workspace/public.git'
168
+ verify_origin gitlab-dir/public 'git@gitlab.com:cgitize-test/public.git'
169
+
170
+ cleanup
171
+ success
172
+ }
173
+
174
+ test_ssh_private() {
175
+ echo
176
+ echo ======================================================================
177
+ echo "${FUNCNAME[0]}"
178
+ echo ======================================================================
179
+
180
+ setup_ssh
181
+ setup_private_repos
182
+ cgitize
183
+
184
+ verify_repos \
185
+ lens \
186
+ chromiumembedded/cef \
187
+ graphviz \
188
+ wireguard/wintun \
189
+ goauthentik-dir/authentik \
190
+ github-dir/public \
191
+ github-dir/private \
192
+ bitbucket-dir/public \
193
+ bitbucket-dir/private \
154
194
  gitlab-dir/public \
195
+ gitlab-dir/private
155
196
 
156
197
  verify_origin lens 'git@github.com:ekmett/lens.git'
157
198
  verify_origin chromiumembedded/cef 'git@bitbucket.org:chromiumembedded/cef.git'
@@ -159,8 +200,11 @@ test_ssh() {
159
200
  verify_origin goauthentik-dir/authentik 'git@github.com:goauthentik/authentik.git'
160
201
 
161
202
  verify_origin github-dir/public 'git@github.com:cgitize-test/public.git'
203
+ verify_origin github-dir/private 'git@github.com:cgitize-test/private.git'
162
204
  verify_origin bitbucket-dir/public 'git@bitbucket.org:cgitize-test-workspace/public.git'
205
+ verify_origin bitbucket-dir/private 'git@bitbucket.org:cgitize-test-workspace/private.git'
163
206
  verify_origin gitlab-dir/public 'git@gitlab.com:cgitize-test/public.git'
207
+ verify_origin gitlab-dir/private 'git@gitlab.com:cgitize-test/private.git'
164
208
 
165
209
  cleanup
166
210
  success
@@ -195,9 +239,9 @@ test_https() {
195
239
  verify_origin graphviz 'https://gitlab.com/graphviz/graphviz.git'
196
240
  verify_origin goauthentik-dir/authentik 'https://github.com/goauthentik/authentik.git'
197
241
 
198
- verify_origin github-dir/public 'https://github.com/cgitize-test/public.git'
199
- verify_origin bitbucket-dir/public 'https://bitbucket.org/cgitize-test-workspace/public.git'
200
- verify_origin gitlab-dir/public 'https://gitlab.com/cgitize-test/public.git'
242
+ verify_origin github-dir/public 'https://github.com/cgitize-test/public.git'
243
+ verify_origin bitbucket-dir/public 'https://bitbucket.org/cgitize-test-workspace/public.git'
244
+ verify_origin gitlab-dir/public 'https://gitlab.com/cgitize-test/public.git'
201
245
 
202
246
  cleanup
203
247
  success
@@ -259,6 +303,7 @@ test_one_repo() {
259
303
  main() {
260
304
  trap cleanup EXIT
261
305
  test_ssh
306
+ test_ssh_private
262
307
  test_https
263
308
  test_https_private
264
309
  test_one_repo
@@ -1,3 +1,5 @@
1
+ IFS= read -r -s -p 'Bitbucket email: ' CGITIZE_BITBUCKET_EMAIL
2
+ echo
1
3
  IFS= read -r -s -p 'Bitbucket token: ' CGITIZE_BITBUCKET_TOKEN
2
4
  echo
3
5
  IFS= read -r -s -p 'GitHub token: ' CGITIZE_GITHUB_TOKEN
@@ -5,7 +7,7 @@ echo
5
7
  IFS= read -r -s -p 'GitLab token: ' CGITIZE_GITLAB_TOKEN
6
8
  echo
7
9
 
8
- export CGITIZE_BITBUCKET_USERNAME=cgitize-test
10
+ export CGITIZE_BITBUCKET_EMAIL
9
11
  export CGITIZE_BITBUCKET_TOKEN
10
12
  export CGITIZE_GITHUB_USERNAME=cgitize-test
11
13
  export CGITIZE_GITHUB_TOKEN
@@ -13,7 +13,7 @@ from requests.exceptions import HTTPError
13
13
  class BitbucketTests(unittest.TestCase):
14
14
  def setUp(self):
15
15
  self.bitbucket = Cloud(
16
- username=os.environ.get('CGITIZE_BITBUCKET_USERNAME'),
16
+ username=os.environ.get('CGITIZE_BITBUCKET_EMAIL'),
17
17
  password=os.environ.get('CGITIZE_BITBUCKET_TOKEN'),
18
18
  cloud=True,
19
19
  )
@@ -65,6 +65,21 @@ class BitbucketTests(unittest.TestCase):
65
65
  r.get_link('html'), 'https://bitbucket.org/cgitize-test-workspace/private'
66
66
  )
67
67
 
68
+ clone_urls = [
69
+ link for link in r.data['links']['clone'] if link['name'] == 'https'
70
+ ]
71
+ self.assertEqual(len(clone_urls), 1)
72
+ self.assertEqual(
73
+ clone_urls[0]['href'],
74
+ 'https://cgitize-test@bitbucket.org/cgitize-test-workspace/private.git',
75
+ )
76
+
77
+ ssh_urls = [link for link in r.data['links']['clone'] if link['name'] == 'ssh']
78
+ self.assertEqual(len(ssh_urls), 1)
79
+ self.assertEqual(
80
+ ssh_urls[0]['href'], 'git@bitbucket.org:cgitize-test-workspace/private.git'
81
+ )
82
+
68
83
  def test_user(self):
69
84
  w = self.bitbucket.workspaces.get('cgitize-test-workspace')
70
85
  self.assertEqual(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes