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.
- {cgitize-6.0.3 → cgitize-6.0.5}/.github/workflows/ci.yml +14 -0
- cgitize-6.0.5/.github/workflows/maintenance.yml +37 -0
- cgitize-6.0.5/.pylintrc +10 -0
- {cgitize-6.0.3/cgitize.egg-info → cgitize-6.0.5}/PKG-INFO +2 -3
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/bitbucket.py +3 -1
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/cgit.py +23 -5
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/config.py +12 -4
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/git.py +18 -7
- cgitize-6.0.5/cgitize/header.py +18 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/main.py +24 -26
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/repo.py +47 -13
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/utils.py +5 -3
- {cgitize-6.0.3 → cgitize-6.0.5/cgitize.egg-info}/PKG-INFO +2 -3
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/SOURCES.txt +2 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/Dockerfile +1 -1
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/Dockerfile +1 -1
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/get_output_dir.py +1 -2
- {cgitize-6.0.3 → cgitize-6.0.5}/pyproject.toml +8 -3
- {cgitize-6.0.3 → cgitize-6.0.5}/requirements.txt +7 -7
- {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/test_bitbucket.py +24 -8
- {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/test_gitlab.py +8 -4
- cgitize-6.0.3/.pylintrc +0 -3
- {cgitize-6.0.3 → cgitize-6.0.5}/.dockerignore +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/.gitattributes +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/.github/dependabot.yml +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/.gitignore +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/.python-version +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/DEVELOPMENT.md +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/LICENSE.txt +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/Makefile +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/README.md +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/__init__.py +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/github.py +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/gitlab.py +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize/version.py +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/dependency_links.txt +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/entry_points.txt +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/requires.txt +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/cgitize.egg-info/top_level.txt +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/README.md +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/cgitize.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/cmd.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/etc/cgit/common +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/etc/cgitrc +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/etc/nginx/conf.d/default.conf +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/about-formatting.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/html-converters/.gitattributes +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/html-converters/md2html +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker/in_cron.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docker-compose.yml +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docs/api.md +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/docs/auth.md +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/examples/cgitize.toml +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/examples/docker/cgitize.toml +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/prelude.mk +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/setup.cfg +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/__init__.py +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/.dockerignore +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/cgitize/.gitignore +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/cgitize/etc/cgitize.toml +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/docker-compose.yml +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/git_server/Dockerfile +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/git_server/setup_repo.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/docker/test.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/example/test.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/integration/local/test.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/set_credentials.sh +0 -0
- {cgitize-6.0.3 → cgitize-6.0.5}/test/unit/__init__.py +0 -0
- {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
|
cgitize-6.0.5/.pylintrc
ADDED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cgitize
|
|
3
|
-
Version: 6.0.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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'] =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
parser.add_argument(
|
|
31
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
25
|
-
|
|
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(
|
|
40
|
-
|
|
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 = [
|
|
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 = [
|
|
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(
|
|
64
|
-
|
|
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(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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(
|
|
41
|
-
|
|
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
|
+
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
|
|
40
|
+
ENV SSH_AUTH_SOCK="$ssh_sock_path"
|
|
41
41
|
|
|
42
42
|
COPY ["docker/cgitize.sh", "/"]
|
|
43
43
|
COPY ["docker/get_output_dir.py", "/"]
|
|
@@ -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
|
-
|
|
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 =
|
|
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.
|
|
3
|
+
certifi==2026.4.22
|
|
4
4
|
cffi==2.0.0
|
|
5
|
-
charset-normalizer==3.4.
|
|
6
|
-
cryptography==
|
|
5
|
+
charset-normalizer==3.4.7
|
|
6
|
+
cryptography==48.0.0
|
|
7
7
|
Deprecated==1.3.1
|
|
8
|
-
idna==3.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
33
|
+
self.assertEqual(
|
|
34
|
+
r.get_link('html'), 'https://bitbucket.org/cgitize-test-workspace/public'
|
|
35
|
+
)
|
|
33
36
|
|
|
34
|
-
clone_urls = [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
55
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
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
|
{cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/html-converters/.gitattributes
RENAMED
|
File without changes
|
{cgitize-6.0.3 → cgitize-6.0.5}/docker/frontend/usr/lib/cgit/filters/html-converters/md2html
RENAMED
|
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
|