cgitize 6.0.3__tar.gz → 6.0.5__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 (69) hide show
  1. {cgitize-6.0.3 → cgitize-6.0.5}/.github/workflows/ci.yml +14 -0
  2. cgitize-6.0.5/.github/workflows/maintenance.yml +37 -0
  3. cgitize-6.0.5/.pylintrc +10 -0
  4. {cgitize-6.0.3/cgitize.egg-info → cgitize-6.0.5}/PKG-INFO +2 -3
  5. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/bitbucket.py +3 -1
  6. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/cgit.py +23 -5
  7. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/config.py +12 -4
  8. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/git.py +18 -7
  9. cgitize-6.0.5/cgitize/header.py +18 -0
  10. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/main.py +24 -26
  11. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/repo.py +47 -13
  12. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/utils.py +5 -3
  13. {cgitize-6.0.3 → cgitize-6.0.5/cgitize.egg-info}/PKG-INFO +2 -3
  14. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/SOURCES.txt +2 -0
  15. {cgitize-6.0.3 → cgitize-6.0.5}/docker/Dockerfile +1 -1
  16. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/Dockerfile +1 -1
  17. {cgitize-6.0.3 → cgitize-6.0.5}/docker/get_output_dir.py +1 -2
  18. {cgitize-6.0.3 → cgitize-6.0.5}/pyproject.toml +8 -3
  19. {cgitize-6.0.3 → cgitize-6.0.5}/requirements.txt +7 -7
  20. {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/test_bitbucket.py +24 -8
  21. {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/test_gitlab.py +8 -4
  22. cgitize-6.0.3/.pylintrc +0 -3
  23. {cgitize-6.0.3 → cgitize-6.0.5}/.dockerignore +0 -0
  24. {cgitize-6.0.3 → cgitize-6.0.5}/.gitattributes +0 -0
  25. {cgitize-6.0.3 → cgitize-6.0.5}/.github/dependabot.yml +0 -0
  26. {cgitize-6.0.3 → cgitize-6.0.5}/.gitignore +0 -0
  27. {cgitize-6.0.3 → cgitize-6.0.5}/.python-version +0 -0
  28. {cgitize-6.0.3 → cgitize-6.0.5}/DEVELOPMENT.md +0 -0
  29. {cgitize-6.0.3 → cgitize-6.0.5}/LICENSE.txt +0 -0
  30. {cgitize-6.0.3 → cgitize-6.0.5}/Makefile +0 -0
  31. {cgitize-6.0.3 → cgitize-6.0.5}/README.md +0 -0
  32. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/__init__.py +0 -0
  33. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/github.py +0 -0
  34. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/gitlab.py +0 -0
  35. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/version.py +0 -0
  36. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/dependency_links.txt +0 -0
  37. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/entry_points.txt +0 -0
  38. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/requires.txt +0 -0
  39. {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/top_level.txt +0 -0
  40. {cgitize-6.0.3 → cgitize-6.0.5}/docker/README.md +0 -0
  41. {cgitize-6.0.3 → cgitize-6.0.5}/docker/cgitize.sh +0 -0
  42. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/cmd.sh +0 -0
  43. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/etc/cgit/common +0 -0
  44. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/etc/cgitrc +0 -0
  45. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/etc/nginx/conf.d/default.conf +0 -0
  46. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/about-formatting.sh +0 -0
  47. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/html-converters/.gitattributes +0 -0
  48. {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/html-converters/md2html +0 -0
  49. {cgitize-6.0.3 → cgitize-6.0.5}/docker/in_cron.sh +0 -0
  50. {cgitize-6.0.3 → cgitize-6.0.5}/docker-compose.yml +0 -0
  51. {cgitize-6.0.3 → cgitize-6.0.5}/docs/api.md +0 -0
  52. {cgitize-6.0.3 → cgitize-6.0.5}/docs/auth.md +0 -0
  53. {cgitize-6.0.3 → cgitize-6.0.5}/examples/cgitize.toml +0 -0
  54. {cgitize-6.0.3 → cgitize-6.0.5}/examples/docker/cgitize.toml +0 -0
  55. {cgitize-6.0.3 → cgitize-6.0.5}/prelude.mk +0 -0
  56. {cgitize-6.0.3 → cgitize-6.0.5}/setup.cfg +0 -0
  57. {cgitize-6.0.3 → cgitize-6.0.5}/test/__init__.py +0 -0
  58. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/.dockerignore +0 -0
  59. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/cgitize/.gitignore +0 -0
  60. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/cgitize/etc/cgitize.toml +0 -0
  61. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/docker-compose.yml +0 -0
  62. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/git_server/Dockerfile +0 -0
  63. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/git_server/setup_repo.sh +0 -0
  64. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/test.sh +0 -0
  65. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/example/test.sh +0 -0
  66. {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/local/test.sh +0 -0
  67. {cgitize-6.0.3 → cgitize-6.0.5}/test/set_credentials.sh +0 -0
  68. {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/__init__.py +0 -0
  69. {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/test_github.py +0 -0
@@ -7,7 +7,21 @@ on:
7
7
  workflow_dispatch:
8
8
 
9
9
  jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ name: Linting
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v6
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v6
18
+ with:
19
+ python-version: '3.x'
20
+ - name: Run black
21
+ uses: psf/black@stable
22
+
10
23
  test_local:
24
+ needs: [lint]
11
25
  runs-on: ubuntu-latest
12
26
  strategy:
13
27
  matrix:
@@ -0,0 +1,37 @@
1
+ name: Maintenance
2
+
3
+ on:
4
+ schedule:
5
+ # Weekly, at 02:30 UTC on Friday (somewhat randomly chosen).
6
+ - cron: '30 02 * * 5'
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ maintenance:
11
+ runs-on: ubuntu-latest
12
+ name: Maintenance
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v6
16
+ with:
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:
21
+ token: '${{ secrets.GH_TOKEN }}'
22
+ - name: Set up ssh-agent
23
+ uses: webfactory/ssh-agent@v0.10.0
24
+ with:
25
+ ssh-private-key: '${{ secrets.SSH_KEY }}'
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v6
28
+ with:
29
+ python-version-file: .python-version
30
+ - name: Run maintenance
31
+ id: maintenance
32
+ run: |
33
+ # See https://github.com/actions/checkout#push-a-commit-using-the-built-in-token
34
+ git config user.name "github-actions[bot]"
35
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
36
+
37
+ make maintenance
@@ -0,0 +1,10 @@
1
+ [MAIN]
2
+ # Enable Pylint to be run in the venv:
3
+ init-hook=
4
+ try: import pylint_venv
5
+ except ImportError: pass
6
+ else: pylint_venv.inithook()
7
+
8
+ [MESSAGES CONTROL]
9
+
10
+ disable=invalid-name,missing-docstring,too-few-public-methods,too-many-arguments
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgitize
3
- Version: 6.0.3
3
+ Version: 6.0.5
4
4
  Summary: Self-host your repositories using cgit
5
5
  Author-email: Egor Tensin <egor@tensin.name>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/egor-tensin/cgitize
8
8
  Project-URL: Bug Tracker, https://github.com/egor-tensin/cgitize/issues
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
- Classifier: License :: OSI Approved :: MIT License
11
10
  Classifier: Programming Language :: Python :: 3
12
11
  Classifier: Topic :: Software Development :: Version Control :: Git
13
12
  Requires-Python: >=3.9
@@ -19,7 +19,9 @@ class Bitbucket:
19
19
  try:
20
20
  parts = repo.id.split('/')
21
21
  if len(parts) != 2:
22
- raise ValueError(f'repository ID must be in the USER/NAME format: {repo.id}')
22
+ raise ValueError(
23
+ f'repository ID must be in the USER/NAME format: {repo.id}'
24
+ )
23
25
  user, name = parts
24
26
  return self._impl.repositories.get(user, name)
25
27
  except HTTPError:
@@ -70,9 +70,17 @@ class AgeFile:
70
70
  # Except I think that the committer date, not author date better
71
71
  # represents activity.
72
72
  with chdir(repo_dir):
73
- success, output = Git.capture('for-each-ref', '--sort=-committerdate', '--count=1', '--format=%(committerdate:iso8601)')
73
+ success, output = Git.capture(
74
+ 'for-each-ref',
75
+ '--sort=-committerdate',
76
+ '--count=1',
77
+ '--format=%(committerdate:iso8601)',
78
+ )
74
79
  if not success:
75
- logging.error("Couldn't get the timestamp of the newest commit in repository: %s", repo_dir)
80
+ logging.error(
81
+ "Couldn't get the timestamp of the newest commit in repository: %s",
82
+ repo_dir,
83
+ )
76
84
  return None
77
85
  return output
78
86
 
@@ -118,7 +126,10 @@ class CGitRepositories:
118
126
  success, output = Git.capture('rev-parse', '--is-inside-work-tree')
119
127
  if not success:
120
128
  # Overwrite the existing directory if it's not a Git repository.
121
- logging.warning('Local directory is not a repository, going to overwrite it: %s', repo_dir)
129
+ logging.warning(
130
+ 'Local directory is not a repository, going to overwrite it: %s',
131
+ repo_dir,
132
+ )
122
133
  return self._mirror(repo)
123
134
 
124
135
  success, output = Git.capture('config', '--get', 'remote.origin.url')
@@ -126,14 +137,21 @@ class CGitRepositories:
126
137
  # Every repository managed by this script should have the
127
138
  # 'origin' remote. If it doesn't, it's trash. Overwrite the
128
139
  # existing directory, mirroring the repository in it.
129
- logging.warning("Local repository doesn't have remote 'origin', going to overwrite it: %s", repo_dir)
140
+ logging.warning(
141
+ "Local repository doesn't have remote 'origin', going to overwrite it: %s",
142
+ repo_dir,
143
+ )
130
144
  return self._mirror(repo)
131
145
 
132
146
  if f'{repo.clone_url}\n' != output:
133
147
  # Jeez, there's a proper local repository in the target
134
148
  # directory already with a different upstream; something's
135
149
  # wrong, fix it manually.
136
- logging.warning("Existing repository '%s' doesn't match the specified clone URL: %s", repo.name, repo.clone_url)
150
+ logging.warning(
151
+ "Existing repository '%s' doesn't match the specified clone URL: %s",
152
+ repo.name,
153
+ repo.clone_url,
154
+ )
137
155
  if self.force:
138
156
  # Unless --force was specified, in which case we overwrite
139
157
  # the repository.
@@ -41,7 +41,9 @@ class MainSection(Section):
41
41
 
42
42
  @property
43
43
  def output_dir(self):
44
- return self._get_config_path('output_dir', default=MainSection.DEFAULT_OUTPUT_DIR)
44
+ return self._get_config_path(
45
+ 'output_dir', default=MainSection.DEFAULT_OUTPUT_DIR
46
+ )
45
47
 
46
48
  @property
47
49
  def error_header_path(self):
@@ -126,7 +128,9 @@ class GitHubSection(ServiceSection):
126
128
  username = self.username
127
129
  token = self.token
128
130
  if (username is None) != (token is None):
129
- raise RuntimeError('please set either both the GitHub username & token, or neither')
131
+ raise RuntimeError(
132
+ 'please set either both the GitHub username & token, or neither'
133
+ )
130
134
  return GitHub(username, token)
131
135
 
132
136
 
@@ -153,7 +157,9 @@ class BitbucketSection(ServiceSection):
153
157
  username = self.username
154
158
  token = self.token
155
159
  if (username is None) != (token is None):
156
- raise RuntimeError('please set either both the Bitbucket username & token, or neither')
160
+ raise RuntimeError(
161
+ 'please set either both the Bitbucket username & token, or neither'
162
+ )
157
163
  return Bitbucket(username, token)
158
164
 
159
165
 
@@ -174,7 +180,9 @@ class GitLabSection(ServiceSection):
174
180
  username = self.username
175
181
  token = self.token
176
182
  if (username is None) != (token is None):
177
- raise RuntimeError('please set either both the GitLab username & token, or neither')
183
+ raise RuntimeError(
184
+ 'please set either both the GitLab username & token, or neither'
185
+ )
178
186
  return GitLab(token)
179
187
 
180
188
 
@@ -8,9 +8,10 @@ import os
8
8
 
9
9
  from cgitize import utils
10
10
 
11
-
12
11
  GIT_ENV = os.environ.copy()
13
- GIT_ENV['GIT_SSH_COMMAND'] = 'ssh -oBatchMode=yes -oLogLevel=QUIET -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null'
12
+ GIT_ENV['GIT_SSH_COMMAND'] = (
13
+ 'ssh -oBatchMode=yes -oLogLevel=QUIET -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null'
14
+ )
14
15
 
15
16
 
16
17
  class Config:
@@ -55,7 +56,9 @@ class Config:
55
56
  for c in name:
56
57
  if c.isalnum() or c == '-' or c == '.':
57
58
  continue
58
- raise RuntimeError(f'section names must only contain alphanumeric characters, . or -: {name}')
59
+ raise RuntimeError(
60
+ f'section names must only contain alphanumeric characters, . or -: {name}'
61
+ )
59
62
 
60
63
  @staticmethod
61
64
  def format_name(name):
@@ -89,7 +92,9 @@ class Config:
89
92
  return f'"{name}"'
90
93
 
91
94
  def format(self):
92
- result = f'[{Config.Section.format_name(self.section)} {self.format_name()}]\n'
95
+ result = (
96
+ f'[{Config.Section.format_name(self.section)} {self.format_name()}]\n'
97
+ )
93
98
  result += ''.join((var.format() for var in self.variables))
94
99
  return result
95
100
 
@@ -107,9 +112,13 @@ class Config:
107
112
  for c in name:
108
113
  if c.isalnum() or c == '-':
109
114
  continue
110
- raise RuntimeError(f'variable name can only contain alphanumeric characters or -: {name}')
115
+ raise RuntimeError(
116
+ f'variable name can only contain alphanumeric characters or -: {name}'
117
+ )
111
118
  if not name[0].isalnum():
112
- raise RuntimeError(f'variable name must start with an alphanumeric character: {name}')
119
+ raise RuntimeError(
120
+ f'variable name must start with an alphanumeric character: {name}'
121
+ )
113
122
 
114
123
  @staticmethod
115
124
  def validate_value(value):
@@ -161,7 +170,9 @@ class Git:
161
170
  with utils.protected_file(config.path):
162
171
  with config.backup() as old_contents:
163
172
  variables = [Config.Variable('insteadOf', repo.clone_url)]
164
- subsection = Config.Subsection('url', repo.clone_url_with_auth, variables)
173
+ subsection = Config.Subsection(
174
+ 'url', repo.clone_url_with_auth, variables
175
+ )
165
176
  new_contents = f'{old_contents}\n{subsection.format()}'
166
177
  config.write(new_contents)
167
178
  yield
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2026 Egor Tensin <egor@tensin.name>
2
+ # This file is part of the "cgitize" project.
3
+ # For details, see https://github.com/egor-tensin/cgitize.
4
+ # Distributed under the MIT License.
5
+
6
+
7
+ def update(path, success, error=None):
8
+ with open(path, 'w') as file:
9
+ contents = ''
10
+ if not success:
11
+ contents = (
12
+ '''<p style="text-align: center; color: red; font-weight: bold;">'''
13
+ )
14
+ contents += '''Some repositories couldn't be updated, please check application logs for details.'''
15
+ if error is not None:
16
+ contents += f'''<br>{type(error).__name__}: {error}'''
17
+ contents += '''</p>\n'''
18
+ file.write(contents)
@@ -9,6 +9,7 @@ import sys
9
9
 
10
10
  from cgitize.cgit import CGitRepositories, CGitServer
11
11
  from cgitize.config import Config
12
+ from cgitize.header import update as update_header
12
13
  from cgitize.utils import setup_logging
13
14
  from cgitize.version import __version__
14
15
 
@@ -17,33 +18,28 @@ def parse_args(argv=None):
17
18
  if argv is None:
18
19
  argv = sys.argv[1:]
19
20
  parser = ArgumentParser()
20
- parser.add_argument('--config', '-c', metavar='PATH',
21
- default=Config.DEFAULT_PATH,
22
- help='config file path')
23
- parser.add_argument('--repo', metavar='REPO_ID',
24
- nargs='*', dest='repos',
25
- help='repos to pull')
26
- parser.add_argument('--force', '-f', action='store_true',
27
- help='overwrite existing repositories')
28
- parser.add_argument('--verbose', '-v', action='store_true',
29
- help='verbose log output')
30
- parser.add_argument('--version', '-V', action='version',
31
- version=f'%(prog)s {__version__}')
21
+ parser.add_argument(
22
+ '--config',
23
+ '-c',
24
+ metavar='PATH',
25
+ default=Config.DEFAULT_PATH,
26
+ help='config file path',
27
+ )
28
+ parser.add_argument(
29
+ '--repo', metavar='REPO_ID', nargs='*', dest='repos', help='repos to pull'
30
+ )
31
+ parser.add_argument(
32
+ '--force', '-f', action='store_true', help='overwrite existing repositories'
33
+ )
34
+ parser.add_argument(
35
+ '--verbose', '-v', action='store_true', help='verbose log output'
36
+ )
37
+ parser.add_argument(
38
+ '--version', '-V', action='version', version=f'%(prog)s {__version__}'
39
+ )
32
40
  return parser.parse_args(argv)
33
41
 
34
42
 
35
- def setup_error_header_file(error_header_path, success, error=None):
36
- with open(error_header_path, 'w') as file:
37
- contents = ''
38
- if not success:
39
- contents = '''<p style="text-align: center; color: red; font-weight: bold;">'''
40
- contents += '''Some repositories couldn't be updated, please check application logs for details.'''
41
- if error is not None:
42
- contents += f'''<br>{type(error).__name__}: {error}'''
43
- contents += '''</p>\n'''
44
- file.write(contents)
45
-
46
-
47
43
  def main(argv=None):
48
44
  args = parse_args(argv)
49
45
  with setup_logging(args.verbose):
@@ -53,7 +49,9 @@ def main(argv=None):
53
49
 
54
50
  try:
55
51
  cgit_server = CGitServer(config.main.clone_url)
56
- output = CGitRepositories(config.main.output_dir, cgit_server, force=args.force)
52
+ output = CGitRepositories(
53
+ config.main.output_dir, cgit_server, force=args.force
54
+ )
57
55
  for repo in config.parse_repositories():
58
56
  if args.repos is None or repo.name in args.repos:
59
57
  success = success and output.update(repo)
@@ -68,7 +66,7 @@ def main(argv=None):
68
66
  if error is not None:
69
67
  logging.warning('Error: %s', error)
70
68
 
71
- setup_error_header_file(config.main.error_header_path, success, error)
69
+ update_header(config.main.error_header_path, success, error)
72
70
  return int(not success)
73
71
 
74
72
 
@@ -21,8 +21,9 @@ class Repo:
21
21
  raise ValueError("every repository must have 'clone_url'")
22
22
  clone_url = src['clone_url']
23
23
  subdir = src.get('dir')
24
- return Repo(name, clone_url, owner=owner, desc=desc, homepage=homepage,
25
- subdir=subdir)
24
+ return Repo(
25
+ name, clone_url, owner=owner, desc=desc, homepage=homepage, subdir=subdir
26
+ )
26
27
 
27
28
  @staticmethod
28
29
  def from_github(src, config, subdir=None):
@@ -36,8 +37,15 @@ class Repo:
36
37
  clone_url = ssh_url if config.main.clone_via_ssh else https_url
37
38
  url_auth = None if config.main.clone_via_ssh else config.github.url_auth
38
39
 
39
- return Repo(name, clone_url, owner=owner, desc=desc, homepage=homepage,
40
- url_auth=url_auth, subdir=subdir)
40
+ return Repo(
41
+ name,
42
+ clone_url,
43
+ owner=owner,
44
+ desc=desc,
45
+ homepage=homepage,
46
+ url_auth=url_auth,
47
+ subdir=subdir,
48
+ )
41
49
 
42
50
  @staticmethod
43
51
  def from_bitbucket(src, config, subdir=None):
@@ -46,13 +54,17 @@ class Repo:
46
54
  homepage = src.get_link('html')
47
55
  owner = src.data['owner']['display_name']
48
56
 
49
- https_urls = [link for link in src.data['links']['clone'] if link['name'] == 'https']
57
+ https_urls = [
58
+ link for link in src.data['links']['clone'] if link['name'] == 'https'
59
+ ]
50
60
  if len(https_urls) != 1:
51
61
  raise RuntimeError(f"no https:// clone URL for repository '{name}'?!")
52
62
  # Bitbucket leaves the username in the URL... Sigh.
53
63
  https_url = url_remove_auth(https_urls[0]['href'])
54
64
 
55
- ssh_urls = [link for link in src.data['links']['clone'] if link ['name'] == 'ssh']
65
+ ssh_urls = [
66
+ link for link in src.data['links']['clone'] if link['name'] == 'ssh'
67
+ ]
56
68
  if len(ssh_urls) != 1:
57
69
  raise RuntimeError(f"no ssh:// clone URL for repository '{name}'?!")
58
70
  ssh_url = ssh_urls[0]['href']
@@ -60,8 +72,15 @@ class Repo:
60
72
  clone_url = ssh_url if config.main.clone_via_ssh else https_url
61
73
  url_auth = None if config.main.clone_via_ssh else config.bitbucket.url_auth
62
74
 
63
- return Repo(name, clone_url, owner=owner, desc=desc, homepage=homepage,
64
- url_auth=url_auth, subdir=subdir)
75
+ return Repo(
76
+ name,
77
+ clone_url,
78
+ owner=owner,
79
+ desc=desc,
80
+ homepage=homepage,
81
+ url_auth=url_auth,
82
+ subdir=subdir,
83
+ )
65
84
 
66
85
  @staticmethod
67
86
  def from_gitlab(src, config, subdir=None):
@@ -75,11 +94,26 @@ class Repo:
75
94
  clone_url = ssh_url if config.main.clone_via_ssh else https_url
76
95
  url_auth = None if config.main.clone_via_ssh else config.gitlab.url_auth
77
96
 
78
- return Repo(name, clone_url, owner=owner, desc=desc, homepage=homepage,
79
- url_auth=url_auth, subdir=subdir)
80
-
81
- def __init__(self, name, clone_url, owner=None, desc=None, homepage=None,
82
- url_auth=None, subdir=None):
97
+ return Repo(
98
+ name,
99
+ clone_url,
100
+ owner=owner,
101
+ desc=desc,
102
+ homepage=homepage,
103
+ url_auth=url_auth,
104
+ subdir=subdir,
105
+ )
106
+
107
+ def __init__(
108
+ self,
109
+ name,
110
+ clone_url,
111
+ owner=None,
112
+ desc=None,
113
+ homepage=None,
114
+ url_auth=None,
115
+ subdir=None,
116
+ ):
83
117
  self._name = name
84
118
  self._desc = desc
85
119
  self._homepage = homepage
@@ -21,7 +21,8 @@ def setup_logging(verbose=False):
21
21
  format='%(asctime)s | %(levelname)s | %(message)s',
22
22
  # Log to stdout, because that's where subprocess's output goes (so that
23
23
  # the don't get interleaved).
24
- stream=sys.stdout)
24
+ stream=sys.stdout,
25
+ )
25
26
  try:
26
27
  yield
27
28
  except Exception as e:
@@ -37,8 +38,9 @@ def run(*args, capture_output=False, **kwargs):
37
38
  stderr = subprocess.STDOUT
38
39
 
39
40
  logging.debug('%s', args)
40
- result = subprocess.run(args, check=True, stdout=stdout, stderr=stderr,
41
- encoding='utf-8', **kwargs)
41
+ result = subprocess.run(
42
+ args, check=True, stdout=stdout, stderr=stderr, encoding='utf-8', **kwargs
43
+ )
42
44
 
43
45
  if result.stdout is not None:
44
46
  logging.debug('\n%s', result.stdout)
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgitize
3
- Version: 6.0.3
3
+ Version: 6.0.5
4
4
  Summary: Self-host your repositories using cgit
5
5
  Author-email: Egor Tensin <egor@tensin.name>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/egor-tensin/cgitize
8
8
  Project-URL: Bug Tracker, https://github.com/egor-tensin/cgitize/issues
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
- Classifier: License :: OSI Approved :: MIT License
11
10
  Classifier: Programming Language :: Python :: 3
12
11
  Classifier: Topic :: Software Development :: Version Control :: Git
13
12
  Requires-Python: >=3.9
@@ -13,6 +13,7 @@ pyproject.toml
13
13
  requirements.txt
14
14
  .github/dependabot.yml
15
15
  .github/workflows/ci.yml
16
+ .github/workflows/maintenance.yml
16
17
  cgitize/__init__.py
17
18
  cgitize/bitbucket.py
18
19
  cgitize/cgit.py
@@ -20,6 +21,7 @@ cgitize/config.py
20
21
  cgitize/git.py
21
22
  cgitize/github.py
22
23
  cgitize/gitlab.py
24
+ cgitize/header.py
23
25
  cgitize/main.py
24
26
  cgitize/repo.py
25
27
  cgitize/utils.py
@@ -37,7 +37,7 @@ ENV PYTHONPATH="/deps:/usr/src"
37
37
 
38
38
  ARG ssh_sock_dir=/
39
39
  ARG ssh_sock_path="$ssh_sock_dir/ssh-agent.sock"
40
- ENV SSH_AUTH_SOCK "$ssh_sock_path"
40
+ ENV SSH_AUTH_SOCK="$ssh_sock_path"
41
41
 
42
42
  COPY ["docker/cgitize.sh", "/"]
43
43
  COPY ["docker/get_output_dir.py", "/"]
@@ -1,4 +1,4 @@
1
- FROM nginx:1.29-alpine AS base
1
+ FROM nginx:1.31-alpine AS base
2
2
 
3
3
  FROM base AS builder
4
4
 
@@ -10,8 +10,7 @@ def parse_args(argv=None):
10
10
  if argv is None:
11
11
  argv = sys.argv[1:]
12
12
  parser = ArgumentParser()
13
- parser.add_argument('config', metavar='PATH',
14
- help='config file path')
13
+ parser.add_argument('config', metavar='PATH', help='config file path')
15
14
  return parser.parse_args(argv)
16
15
 
17
16
 
@@ -1,11 +1,14 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61.0", "setuptools-scm"]
2
+ # The SPDX license identifiers are available from 77.0.3:
3
+ # https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license-and-license-files
4
+ requires = ["setuptools >= 77.0.3", "setuptools-scm"]
3
5
  build-backend = "setuptools.build_meta"
4
6
 
5
7
  [project]
6
8
  name = "cgitize"
7
9
  description = "Self-host your repositories using cgit"
8
- license = {text = "MIT"}
10
+ license = "MIT"
11
+ license-files = ["LICENSE.txt"]
9
12
  dynamic = ["version"]
10
13
  authors = [{name = "Egor Tensin", email = "egor@tensin.name"}]
11
14
  readme = "README.md"
@@ -21,7 +24,6 @@ dependencies = [
21
24
 
22
25
  classifiers = [
23
26
  "Development Status :: 5 - Production/Stable",
24
- "License :: OSI Approved :: MIT License",
25
27
  "Programming Language :: Python :: 3",
26
28
  "Topic :: Software Development :: Version Control :: Git",
27
29
  ]
@@ -37,3 +39,6 @@ cgitize = "cgitize.main:main"
37
39
  packages = ["cgitize"]
38
40
 
39
41
  [tool.setuptools_scm]
42
+
43
+ [tool.black]
44
+ skip-string-normalization = true
@@ -1,23 +1,23 @@
1
1
  atlassian-python-api==4.0.7
2
2
  beautifulsoup4==4.14.3
3
- certifi==2026.2.25
3
+ certifi==2026.4.22
4
4
  cffi==2.0.0
5
- charset-normalizer==3.4.6
6
- cryptography==46.0.6
5
+ charset-normalizer==3.4.7
6
+ cryptography==48.0.0
7
7
  Deprecated==1.3.1
8
- idna==3.11
8
+ idna==3.15
9
9
  jmespath==1.1.0
10
10
  oauthlib==3.3.1
11
11
  pycparser==3.0
12
- PyGithub==2.9.0
12
+ PyGithub==2.9.1
13
13
  PyJWT==2.12.1
14
14
  PyNaCl==1.6.2
15
15
  python-gitlab==6.5.0
16
- requests==2.33.0
16
+ requests==2.34.2
17
17
  requests-oauthlib==2.0.0
18
18
  requests-toolbelt==1.0.0
19
19
  soupsieve==2.8.3
20
20
  tomli==2.4.1
21
21
  typing_extensions==4.15.0
22
- urllib3==2.6.3
22
+ urllib3==2.7.0
23
23
  wrapt==2.1.2
@@ -15,7 +15,8 @@ class BitbucketTests(unittest.TestCase):
15
15
  self.bitbucket = Cloud(
16
16
  username=os.environ.get('CGITIZE_BITBUCKET_USERNAME'),
17
17
  password=os.environ.get('CGITIZE_BITBUCKET_TOKEN'),
18
- cloud=True)
18
+ cloud=True,
19
+ )
19
20
 
20
21
  def test_nonexistent_repo(self):
21
22
  with self.assertRaises(HTTPError):
@@ -29,15 +30,24 @@ class BitbucketTests(unittest.TestCase):
29
30
  self.assertEqual(r.data['workspace']['name'], 'Test cgitize workspace')
30
31
  self.assertEqual(r.data['workspace']['slug'], 'cgitize-test-workspace')
31
32
 
32
- self.assertEqual(r.get_link('html'), 'https://bitbucket.org/cgitize-test-workspace/public')
33
+ self.assertEqual(
34
+ r.get_link('html'), 'https://bitbucket.org/cgitize-test-workspace/public'
35
+ )
33
36
 
34
- clone_urls = [link for link in r.data['links']['clone'] if link['name'] == 'https']
37
+ clone_urls = [
38
+ link for link in r.data['links']['clone'] if link['name'] == 'https'
39
+ ]
35
40
  self.assertEqual(len(clone_urls), 1)
36
- self.assertEqual(clone_urls[0]['href'], 'https://cgitize-test@bitbucket.org/cgitize-test-workspace/public.git')
41
+ self.assertEqual(
42
+ clone_urls[0]['href'],
43
+ 'https://cgitize-test@bitbucket.org/cgitize-test-workspace/public.git',
44
+ )
37
45
 
38
46
  ssh_urls = [link for link in r.data['links']['clone'] if link['name'] == 'ssh']
39
47
  self.assertEqual(len(ssh_urls), 1)
40
- self.assertEqual(ssh_urls[0]['href'], 'git@bitbucket.org:cgitize-test-workspace/public.git')
48
+ self.assertEqual(
49
+ ssh_urls[0]['href'], 'git@bitbucket.org:cgitize-test-workspace/public.git'
50
+ )
41
51
 
42
52
  def test_private_repo(self):
43
53
  r = self.bitbucket.repositories.get('cgitize-test-workspace', 'private')
@@ -47,12 +57,18 @@ class BitbucketTests(unittest.TestCase):
47
57
  self.assertEqual(r.data['workspace']['name'], 'Test cgitize workspace')
48
58
  self.assertEqual(r.data['workspace']['slug'], 'cgitize-test-workspace')
49
59
 
50
- self.assertEqual(r.get_link('html'), 'https://bitbucket.org/cgitize-test-workspace/private')
60
+ self.assertEqual(
61
+ r.get_link('html'), 'https://bitbucket.org/cgitize-test-workspace/private'
62
+ )
51
63
 
52
64
  def test_user(self):
53
65
  w = self.bitbucket.workspaces.get('cgitize-test-workspace')
54
- self.assertEqual(len([r for r in w.repositories.each() if r.name == 'public']), 1)
55
- self.assertEqual(len([r for r in w.repositories.each() if r.name == 'private']), 1)
66
+ self.assertEqual(
67
+ len([r for r in w.repositories.each() if r.name == 'public']), 1
68
+ )
69
+ self.assertEqual(
70
+ len([r for r in w.repositories.each() if r.name == 'private']), 1
71
+ )
56
72
 
57
73
 
58
74
  class BitbucketTestPrivateRepo(unittest.TestCase):
@@ -13,8 +13,8 @@ from gitlab.exceptions import GitlabGetError
13
13
  class GitLabTests(unittest.TestCase):
14
14
  def setUp(self):
15
15
  self.gitlab = Gitlab(
16
- 'https://gitlab.com',
17
- private_token=os.environ.get('CGITIZE_GITLAB_TOKEN'))
16
+ 'https://gitlab.com', private_token=os.environ.get('CGITIZE_GITLAB_TOKEN')
17
+ )
18
18
 
19
19
  def test_nonexistent_repo(self):
20
20
  with self.assertRaises(GitlabGetError):
@@ -27,7 +27,9 @@ class GitLabTests(unittest.TestCase):
27
27
  self.assertEqual(r.namespace['name'], 'Test cgitize user')
28
28
  self.assertEqual(r.namespace['path'], 'cgitize-test')
29
29
  self.assertEqual(r.web_url, 'https://gitlab.com/cgitize-test/public')
30
- self.assertEqual(r.http_url_to_repo, 'https://gitlab.com/cgitize-test/public.git')
30
+ self.assertEqual(
31
+ r.http_url_to_repo, 'https://gitlab.com/cgitize-test/public.git'
32
+ )
31
33
  self.assertEqual(r.ssh_url_to_repo, 'git@gitlab.com:cgitize-test/public.git')
32
34
 
33
35
  def test_private_repo(self):
@@ -37,7 +39,9 @@ class GitLabTests(unittest.TestCase):
37
39
  self.assertEqual(r.namespace['name'], 'Test cgitize user')
38
40
  self.assertEqual(r.namespace['path'], 'cgitize-test')
39
41
  self.assertEqual(r.web_url, 'https://gitlab.com/cgitize-test/private')
40
- self.assertEqual(r.http_url_to_repo, 'https://gitlab.com/cgitize-test/private.git')
42
+ self.assertEqual(
43
+ r.http_url_to_repo, 'https://gitlab.com/cgitize-test/private.git'
44
+ )
41
45
  self.assertEqual(r.ssh_url_to_repo, 'git@gitlab.com:cgitize-test/private.git')
42
46
 
43
47
  def test_user(self):
cgitize-6.0.3/.pylintrc DELETED
@@ -1,3 +0,0 @@
1
- [MESSAGES CONTROL]
2
-
3
- disable=invalid-name,missing-docstring,too-few-public-methods,too-many-arguments
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