cgitize 8.0.0__tar.gz → 8.0.2__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-8.0.2/.git-blame-ignore-revs +1 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/.github/dependabot.yml +1 -4
- {cgitize-8.0.0 → cgitize-8.0.2}/.github/workflows/ci.yml +7 -6
- {cgitize-8.0.0 → cgitize-8.0.2}/.github/workflows/maintenance.yml +3 -2
- {cgitize-8.0.0 → cgitize-8.0.2}/PKG-INFO +1 -1
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/bitbucket.py +2 -2
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/cgit.py +23 -23
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/config.py +35 -35
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/git.py +32 -32
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/github.py +2 -2
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/gitlab.py +1 -1
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/header.py +6 -6
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/main.py +11 -11
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/repo.py +29 -29
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/utils.py +24 -12
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/version.py +2 -2
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize.egg-info/PKG-INFO +1 -1
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize.egg-info/SOURCES.txt +3 -0
- cgitize-8.0.2/cgitize.egg-info/scm_file_list.json +65 -0
- cgitize-8.0.2/cgitize.egg-info/scm_version.json +8 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/get_output_dir.py +2 -2
- {cgitize-8.0.0 → cgitize-8.0.2}/pyproject.toml +0 -3
- {cgitize-8.0.0 → cgitize-8.0.2}/requirements.txt +3 -3
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/git_server/Dockerfile +1 -1
- cgitize-8.0.2/test/unit/test_bitbucket.py +99 -0
- cgitize-8.0.2/test/unit/test_github.py +62 -0
- cgitize-8.0.2/test/unit/test_gitlab.py +65 -0
- cgitize-8.0.0/test/unit/test_bitbucket.py +0 -99
- cgitize-8.0.0/test/unit/test_github.py +0 -62
- cgitize-8.0.0/test/unit/test_gitlab.py +0 -65
- {cgitize-8.0.0 → cgitize-8.0.2}/.dockerignore +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/.gitattributes +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/.gitignore +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/.pylintrc +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/.python-version +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/DEVELOPMENT.md +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/LICENSE.txt +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/Makefile +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/README.md +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize/__init__.py +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize.egg-info/dependency_links.txt +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize.egg-info/entry_points.txt +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize.egg-info/requires.txt +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/cgitize.egg-info/top_level.txt +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/Dockerfile +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/README.md +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/cgitize.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/Dockerfile +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/cmd.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/etc/cgit/common +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/etc/cgitrc +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/etc/nginx/conf.d/default.conf +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/usr/lib/cgit/filters/about-formatting.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/usr/lib/cgit/filters/html-converters/.gitattributes +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/frontend/usr/lib/cgit/filters/html-converters/md2html +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker/in_cron.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docker-compose.yml +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docs/api.md +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/docs/auth.md +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/examples/cgitize.toml +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/examples/docker/cgitize.toml +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/prelude.mk +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/setup.cfg +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/__init__.py +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/.dockerignore +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/cgitize/.gitignore +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/cgitize/etc/cgitize.toml +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/docker-compose.yml +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/git_server/setup_repo.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/docker/test.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/example/test.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/integration/local/test.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/set_credentials.sh +0 -0
- {cgitize-8.0.0 → cgitize-8.0.2}/test/unit/__init__.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ebbab9207fc3ec5dcf2df9011d945a8de2617983
|
|
@@ -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@
|
|
16
|
+
uses: actions/checkout@v7
|
|
16
17
|
- name: Set up Python
|
|
17
18
|
uses: actions/setup-python@v6
|
|
18
19
|
with:
|
|
@@ -35,7 +36,7 @@ jobs:
|
|
|
35
36
|
CGITIZE_GITLAB_TOKEN: '${{ secrets.CGITIZE_GITLAB_TOKEN }}'
|
|
36
37
|
steps:
|
|
37
38
|
- name: Checkout
|
|
38
|
-
uses: actions/checkout@
|
|
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@
|
|
59
|
+
uses: actions/checkout@v7
|
|
59
60
|
- name: Integration test (Docker)
|
|
60
61
|
run: sudo make test/docker
|
|
61
62
|
|
|
@@ -72,7 +73,7 @@ jobs:
|
|
|
72
73
|
CGITIZE_GITLAB_TOKEN: '${{ secrets.CGITIZE_GITLAB_TOKEN }}'
|
|
73
74
|
steps:
|
|
74
75
|
- name: Checkout
|
|
75
|
-
uses: actions/checkout@
|
|
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@
|
|
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@
|
|
157
|
+
uses: actions/checkout@v7
|
|
157
158
|
with:
|
|
158
159
|
fetch-depth: 0
|
|
159
160
|
- name: Set up Python
|
|
@@ -12,10 +12,11 @@ jobs:
|
|
|
12
12
|
name: Maintenance
|
|
13
13
|
steps:
|
|
14
14
|
- name: Checkout
|
|
15
|
-
uses: actions/checkout@
|
|
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:
|
|
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
|
|
19
20
|
token: '${{ secrets.GH_TOKEN }}'
|
|
20
21
|
- name: Set up ssh-agent
|
|
21
22
|
uses: webfactory/ssh-agent@v0.10.0
|
|
@@ -17,10 +17,10 @@ class Bitbucket:
|
|
|
17
17
|
|
|
18
18
|
def get_repo(self, repo):
|
|
19
19
|
try:
|
|
20
|
-
parts = repo.id.split(
|
|
20
|
+
parts = repo.id.split("/")
|
|
21
21
|
if len(parts) != 2:
|
|
22
22
|
raise ValueError(
|
|
23
|
-
f
|
|
23
|
+
f"repository ID must be in the USER/NAME format: {repo.id}"
|
|
24
24
|
)
|
|
25
25
|
user, name = parts
|
|
26
26
|
return self._impl.repositories.get(user, name)
|
|
@@ -27,20 +27,20 @@ class CGitRCWriter:
|
|
|
27
27
|
|
|
28
28
|
@staticmethod
|
|
29
29
|
def get_path(repo_dir):
|
|
30
|
-
return os.path.join(repo_dir,
|
|
30
|
+
return os.path.join(repo_dir, "cgitrc")
|
|
31
31
|
|
|
32
32
|
def write(self, repo_dir, repo):
|
|
33
|
-
with open(self.get_path(repo_dir),
|
|
34
|
-
self._write_field(fd,
|
|
35
|
-
self._write_field(fd,
|
|
36
|
-
self._write_field(fd,
|
|
37
|
-
self._write_field(fd,
|
|
33
|
+
with open(self.get_path(repo_dir), "w") as fd:
|
|
34
|
+
self._write_field(fd, "clone-url", self._build_clone_url(repo))
|
|
35
|
+
self._write_field(fd, "owner", repo.owner)
|
|
36
|
+
self._write_field(fd, "desc", repo.desc)
|
|
37
|
+
self._write_field(fd, "homepage", repo.homepage)
|
|
38
38
|
|
|
39
39
|
@staticmethod
|
|
40
40
|
def _write_field(fd, field, value):
|
|
41
41
|
if value is None:
|
|
42
42
|
return
|
|
43
|
-
fd.write(f
|
|
43
|
+
fd.write(f"{field}={value}\n")
|
|
44
44
|
|
|
45
45
|
def _build_clone_url(self, repo):
|
|
46
46
|
clone_urls = []
|
|
@@ -51,7 +51,7 @@ class CGitRCWriter:
|
|
|
51
51
|
clone_urls.append(repo.clone_url)
|
|
52
52
|
if not clone_urls:
|
|
53
53
|
return None
|
|
54
|
-
clone_urls =
|
|
54
|
+
clone_urls = " ".join(clone_urls)
|
|
55
55
|
return clone_urls
|
|
56
56
|
|
|
57
57
|
|
|
@@ -61,8 +61,8 @@ class AgeFile:
|
|
|
61
61
|
timestamp = AgeFile.get_age(repo_dir)
|
|
62
62
|
if timestamp:
|
|
63
63
|
os.makedirs(AgeFile.get_dir(repo_dir), exist_ok=True)
|
|
64
|
-
with open(AgeFile.get_path(repo_dir), mode=
|
|
65
|
-
fd.write(f
|
|
64
|
+
with open(AgeFile.get_path(repo_dir), mode="w") as fd:
|
|
65
|
+
fd.write(f"{timestamp}")
|
|
66
66
|
|
|
67
67
|
@staticmethod
|
|
68
68
|
def get_age(repo_dir):
|
|
@@ -71,10 +71,10 @@ class AgeFile:
|
|
|
71
71
|
# represents activity.
|
|
72
72
|
with chdir(repo_dir):
|
|
73
73
|
success, output = Git.capture(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
"for-each-ref",
|
|
75
|
+
"--sort=-committerdate",
|
|
76
|
+
"--count=1",
|
|
77
|
+
"--format=%(committerdate:iso8601)",
|
|
78
78
|
)
|
|
79
79
|
if not success:
|
|
80
80
|
logging.error(
|
|
@@ -86,11 +86,11 @@ class AgeFile:
|
|
|
86
86
|
|
|
87
87
|
@staticmethod
|
|
88
88
|
def get_dir(repo_dir):
|
|
89
|
-
return os.path.join(repo_dir,
|
|
89
|
+
return os.path.join(repo_dir, "info", "web")
|
|
90
90
|
|
|
91
91
|
@staticmethod
|
|
92
92
|
def get_path(repo_dir):
|
|
93
|
-
return os.path.join(AgeFile.get_dir(repo_dir),
|
|
93
|
+
return os.path.join(AgeFile.get_dir(repo_dir), "last-modified")
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
class CGitRepositories:
|
|
@@ -123,16 +123,16 @@ class CGitRepositories:
|
|
|
123
123
|
return self._mirror(repo)
|
|
124
124
|
|
|
125
125
|
with chdir(repo_dir):
|
|
126
|
-
success, output = Git.capture(
|
|
126
|
+
success, output = Git.capture("rev-parse", "--is-inside-work-tree")
|
|
127
127
|
if not success:
|
|
128
128
|
# Overwrite the existing directory if it's not a Git repository.
|
|
129
129
|
logging.warning(
|
|
130
|
-
|
|
130
|
+
"Local directory is not a repository, going to overwrite it: %s",
|
|
131
131
|
repo_dir,
|
|
132
132
|
)
|
|
133
133
|
return self._mirror(repo)
|
|
134
134
|
|
|
135
|
-
success, output = Git.capture(
|
|
135
|
+
success, output = Git.capture("config", "--get", "remote.origin.url")
|
|
136
136
|
if not success:
|
|
137
137
|
# Every repository managed by this script should have the
|
|
138
138
|
# 'origin' remote. If it doesn't, it's trash. Overwrite the
|
|
@@ -143,7 +143,7 @@ class CGitRepositories:
|
|
|
143
143
|
)
|
|
144
144
|
return self._mirror(repo)
|
|
145
145
|
|
|
146
|
-
if f
|
|
146
|
+
if f"{repo.clone_url}" != output:
|
|
147
147
|
# Jeez, there's a proper local repository in the target
|
|
148
148
|
# directory already with a different upstream; something's
|
|
149
149
|
# wrong, fix it manually.
|
|
@@ -172,16 +172,16 @@ class CGitRepositories:
|
|
|
172
172
|
logging.exception(e)
|
|
173
173
|
return False
|
|
174
174
|
with Git.setup_auth(repo):
|
|
175
|
-
return Git.check(
|
|
175
|
+
return Git.check("clone", "--mirror", "--quiet", repo.clone_url, repo_dir)
|
|
176
176
|
|
|
177
177
|
def _fix_upstream_url(self, repo):
|
|
178
178
|
repo_dir = self.get_repo_dir(repo)
|
|
179
179
|
with chdir(repo_dir):
|
|
180
|
-
return Git.check(
|
|
180
|
+
return Git.check("remote", "set-url", "origin", repo.clone_url)
|
|
181
181
|
|
|
182
182
|
def _update_existing(self, repo):
|
|
183
183
|
logging.info("Updating repository '%s'", repo.name)
|
|
184
184
|
repo_dir = self.get_repo_dir(repo)
|
|
185
185
|
with chdir(repo_dir):
|
|
186
186
|
with Git.setup_auth(repo):
|
|
187
|
-
return Git.check(
|
|
187
|
+
return Git.check("remote", "update", "--prune")
|
|
@@ -22,7 +22,7 @@ class Section:
|
|
|
22
22
|
def _get_config_value(self, key, required=True, default=None):
|
|
23
23
|
if required and default is None:
|
|
24
24
|
if not key in self.impl:
|
|
25
|
-
raise RuntimeError(f
|
|
25
|
+
raise RuntimeError(f"configuration value is missing: {key}")
|
|
26
26
|
return self.impl.get(key, default)
|
|
27
27
|
|
|
28
28
|
def _get_config_path(self, *args, **kwargs):
|
|
@@ -38,36 +38,36 @@ class Section:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class MainSection(Section):
|
|
41
|
-
DEFAULT_OUTPUT_DIR =
|
|
41
|
+
DEFAULT_OUTPUT_DIR = "/mnt/cgitize"
|
|
42
42
|
|
|
43
43
|
@property
|
|
44
44
|
def output_dir(self):
|
|
45
45
|
return self._get_config_path(
|
|
46
|
-
|
|
46
|
+
"output_dir", default=MainSection.DEFAULT_OUTPUT_DIR
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
50
|
def error_header_path(self):
|
|
51
|
-
return os.path.join(self.output_dir,
|
|
51
|
+
return os.path.join(self.output_dir, "error.html")
|
|
52
52
|
|
|
53
53
|
@property
|
|
54
54
|
def clone_url(self):
|
|
55
|
-
return self._get_config_value(
|
|
55
|
+
return self._get_config_value("clone_url", required=False)
|
|
56
56
|
|
|
57
57
|
@property
|
|
58
58
|
def clone_via_ssh(self):
|
|
59
|
-
return self._get_config_value(
|
|
59
|
+
return self._get_config_value("clone_via_ssh", default=True)
|
|
60
60
|
|
|
61
61
|
@property
|
|
62
62
|
def default_owner(self):
|
|
63
|
-
return self._get_config_value(
|
|
63
|
+
return self._get_config_value("owner", required=False)
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class ServiceSection(Section, ABC):
|
|
67
67
|
def __init__(self, impl):
|
|
68
68
|
super().__init__(impl)
|
|
69
|
-
self.repositories = RepositoriesSection(self.impl.get(
|
|
70
|
-
self.users = UsersSection(self.impl.get(
|
|
69
|
+
self.repositories = RepositoriesSection(self.impl.get("repositories", {}))
|
|
70
|
+
self.users = UsersSection(self.impl.get("users", {}))
|
|
71
71
|
|
|
72
72
|
def _enum_explicit_repositories(self, cfg, api):
|
|
73
73
|
for r in self.repositories.enum_repositories():
|
|
@@ -99,15 +99,15 @@ class ServiceSection(Section, ABC):
|
|
|
99
99
|
class GitHubSection(ServiceSection):
|
|
100
100
|
def __init__(self, impl):
|
|
101
101
|
super().__init__(impl)
|
|
102
|
-
self.orgs = OrgsSection(self.impl.get(
|
|
102
|
+
self.orgs = OrgsSection(self.impl.get("organizations", {}))
|
|
103
103
|
|
|
104
104
|
@property
|
|
105
105
|
def token(self):
|
|
106
|
-
return self._get_config_or_env(
|
|
106
|
+
return self._get_config_or_env("token", "CGITIZE_GITHUB_TOKEN")
|
|
107
107
|
|
|
108
108
|
@property
|
|
109
109
|
def username(self):
|
|
110
|
-
return self._get_config_or_env(
|
|
110
|
+
return self._get_config_or_env("username", "CGITIZE_GITHUB_USERNAME")
|
|
111
111
|
|
|
112
112
|
@property
|
|
113
113
|
def url_auth(self):
|
|
@@ -131,7 +131,7 @@ class GitHubSection(ServiceSection):
|
|
|
131
131
|
token = self.token
|
|
132
132
|
if (username is None) != (token is None):
|
|
133
133
|
raise RuntimeError(
|
|
134
|
-
|
|
134
|
+
"please set either both the GitHub username & token, or neither"
|
|
135
135
|
)
|
|
136
136
|
return GitHub(username, token)
|
|
137
137
|
|
|
@@ -139,11 +139,11 @@ class GitHubSection(ServiceSection):
|
|
|
139
139
|
class BitbucketSection(ServiceSection):
|
|
140
140
|
@property
|
|
141
141
|
def token(self):
|
|
142
|
-
return self._get_config_or_env(
|
|
142
|
+
return self._get_config_or_env("token", "CGITIZE_BITBUCKET_TOKEN")
|
|
143
143
|
|
|
144
144
|
@property
|
|
145
145
|
def username(self):
|
|
146
|
-
return self._get_config_or_env(
|
|
146
|
+
return self._get_config_or_env("email", "CGITIZE_BITBUCKET_EMAIL")
|
|
147
147
|
|
|
148
148
|
@property
|
|
149
149
|
def url_auth(self):
|
|
@@ -154,7 +154,7 @@ class BitbucketSection(ServiceSection):
|
|
|
154
154
|
token = self.token
|
|
155
155
|
if (username is None) != (token is None):
|
|
156
156
|
raise RuntimeError(
|
|
157
|
-
|
|
157
|
+
"please set either both the Bitbucket username & token, or neither"
|
|
158
158
|
)
|
|
159
159
|
return Bitbucket(username, token)
|
|
160
160
|
|
|
@@ -162,11 +162,11 @@ class BitbucketSection(ServiceSection):
|
|
|
162
162
|
class GitLabSection(ServiceSection):
|
|
163
163
|
@property
|
|
164
164
|
def token(self):
|
|
165
|
-
return self._get_config_or_env(
|
|
165
|
+
return self._get_config_or_env("token", "CGITIZE_GITLAB_TOKEN")
|
|
166
166
|
|
|
167
167
|
@property
|
|
168
168
|
def username(self):
|
|
169
|
-
return self._get_config_or_env(
|
|
169
|
+
return self._get_config_or_env("username", "CGITIZE_GITLAB_USERNAME")
|
|
170
170
|
|
|
171
171
|
@property
|
|
172
172
|
def url_auth(self):
|
|
@@ -177,7 +177,7 @@ class GitLabSection(ServiceSection):
|
|
|
177
177
|
token = self.token
|
|
178
178
|
if (username is None) != (token is None):
|
|
179
179
|
raise RuntimeError(
|
|
180
|
-
|
|
180
|
+
"please set either both the GitLab username & token, or neither"
|
|
181
181
|
)
|
|
182
182
|
return GitLab(token)
|
|
183
183
|
|
|
@@ -200,56 +200,56 @@ class RepositoriesSection(Section):
|
|
|
200
200
|
class Entity(ABC):
|
|
201
201
|
@property
|
|
202
202
|
def name(self):
|
|
203
|
-
return self._impl[
|
|
203
|
+
return self._impl["name"]
|
|
204
204
|
|
|
205
205
|
@property
|
|
206
206
|
def dir(self):
|
|
207
|
-
return self._impl.get(
|
|
207
|
+
return self._impl.get("dir")
|
|
208
208
|
|
|
209
209
|
@property
|
|
210
210
|
def skip(self):
|
|
211
|
-
return self._impl.get(
|
|
211
|
+
return self._impl.get("skip", [])
|
|
212
212
|
|
|
213
213
|
|
|
214
214
|
class User(Entity):
|
|
215
215
|
def __init__(self, impl):
|
|
216
|
-
if
|
|
216
|
+
if "name" not in impl:
|
|
217
217
|
raise ValueError("every user must have a 'name'")
|
|
218
218
|
self._impl = impl
|
|
219
219
|
|
|
220
220
|
@property
|
|
221
221
|
def public_repos(self):
|
|
222
|
-
return self._impl.get(
|
|
222
|
+
return self._impl.get("public", True)
|
|
223
223
|
|
|
224
224
|
@property
|
|
225
225
|
def private_repos(self):
|
|
226
|
-
return self._impl.get(
|
|
226
|
+
return self._impl.get("private", False)
|
|
227
227
|
|
|
228
228
|
|
|
229
229
|
class Org(Entity):
|
|
230
230
|
def __init__(self, impl):
|
|
231
|
-
if
|
|
231
|
+
if "name" not in impl:
|
|
232
232
|
raise ValueError("every organization must have a 'name'")
|
|
233
233
|
self._impl = impl
|
|
234
234
|
|
|
235
235
|
|
|
236
236
|
class HostedRepo:
|
|
237
237
|
def __init__(self, impl):
|
|
238
|
-
if
|
|
238
|
+
if "id" not in impl:
|
|
239
239
|
raise ValueError("every hosted repository must have 'id'")
|
|
240
240
|
self._impl = impl
|
|
241
241
|
|
|
242
242
|
@property
|
|
243
243
|
def id(self):
|
|
244
|
-
return self._impl[
|
|
244
|
+
return self._impl["id"]
|
|
245
245
|
|
|
246
246
|
@property
|
|
247
247
|
def dir(self):
|
|
248
|
-
return self._impl.get(
|
|
248
|
+
return self._impl.get("dir")
|
|
249
249
|
|
|
250
250
|
|
|
251
251
|
class Config:
|
|
252
|
-
DEFAULT_PATH =
|
|
252
|
+
DEFAULT_PATH = "/etc/cgitize/cgitize.toml"
|
|
253
253
|
|
|
254
254
|
@staticmethod
|
|
255
255
|
def read(path):
|
|
@@ -257,13 +257,13 @@ class Config:
|
|
|
257
257
|
|
|
258
258
|
def __init__(self, path):
|
|
259
259
|
self.path = os.path.abspath(path)
|
|
260
|
-
with open(self.path,
|
|
260
|
+
with open(self.path, "rb") as f:
|
|
261
261
|
self.impl = tomli.load(f)
|
|
262
262
|
self.main = MainSection(self.impl)
|
|
263
|
-
self.repositories = RepositoriesSection(self.impl.get(
|
|
264
|
-
self.github = GitHubSection(self.impl.get(
|
|
265
|
-
self.bitbucket = BitbucketSection(self.impl.get(
|
|
266
|
-
self.gitlab = GitLabSection(self.impl.get(
|
|
263
|
+
self.repositories = RepositoriesSection(self.impl.get("repositories", {}))
|
|
264
|
+
self.github = GitHubSection(self.impl.get("github", {}))
|
|
265
|
+
self.bitbucket = BitbucketSection(self.impl.get("bitbucket", {}))
|
|
266
|
+
self.gitlab = GitLabSection(self.impl.get("gitlab", {}))
|
|
267
267
|
|
|
268
268
|
def _parse_explicit_repositories(self):
|
|
269
269
|
for r in self.repositories.enum_repositories():
|
|
@@ -9,8 +9,8 @@ import os
|
|
|
9
9
|
from cgitize import utils
|
|
10
10
|
|
|
11
11
|
GIT_ENV = os.environ.copy()
|
|
12
|
-
GIT_ENV[
|
|
13
|
-
|
|
12
|
+
GIT_ENV["GIT_SSH_COMMAND"] = (
|
|
13
|
+
"ssh -oBatchMode=yes -oLogLevel=QUIET -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null"
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
|
|
@@ -21,15 +21,15 @@ class Config:
|
|
|
21
21
|
def exists(self):
|
|
22
22
|
return os.path.exists(self.path)
|
|
23
23
|
|
|
24
|
-
def open(self, mode=
|
|
25
|
-
return open(self.path, mode=mode, encoding=
|
|
24
|
+
def open(self, mode="r"):
|
|
25
|
+
return open(self.path, mode=mode, encoding="utf-8")
|
|
26
26
|
|
|
27
27
|
def read(self):
|
|
28
|
-
with self.open(mode=
|
|
28
|
+
with self.open(mode="r") as fd:
|
|
29
29
|
return fd.read()
|
|
30
30
|
|
|
31
31
|
def write(self, contents):
|
|
32
|
-
with self.open(mode=
|
|
32
|
+
with self.open(mode="w") as fd:
|
|
33
33
|
fd.write(contents)
|
|
34
34
|
|
|
35
35
|
@contextmanager
|
|
@@ -52,12 +52,12 @@ class Config:
|
|
|
52
52
|
@staticmethod
|
|
53
53
|
def validate_name(name):
|
|
54
54
|
if not name:
|
|
55
|
-
raise RuntimeError(
|
|
55
|
+
raise RuntimeError("section names cannot be empty")
|
|
56
56
|
for c in name:
|
|
57
|
-
if c.isalnum() or c ==
|
|
57
|
+
if c.isalnum() or c == "-" or c == ".":
|
|
58
58
|
continue
|
|
59
59
|
raise RuntimeError(
|
|
60
|
-
f
|
|
60
|
+
f"section names must only contain alphanumeric characters, . or -: {name}"
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
@staticmethod
|
|
@@ -65,8 +65,8 @@ class Config:
|
|
|
65
65
|
return name
|
|
66
66
|
|
|
67
67
|
def format(self):
|
|
68
|
-
result = f
|
|
69
|
-
result +=
|
|
68
|
+
result = f"[{self.format_name(self.name)}]\n"
|
|
69
|
+
result += "".join((var.format() for var in self.variables))
|
|
70
70
|
return result
|
|
71
71
|
|
|
72
72
|
class Subsection:
|
|
@@ -79,23 +79,23 @@ class Config:
|
|
|
79
79
|
|
|
80
80
|
@staticmethod
|
|
81
81
|
def validate_name(name):
|
|
82
|
-
if
|
|
83
|
-
raise RuntimeError(f
|
|
82
|
+
if "\n" in name:
|
|
83
|
+
raise RuntimeError(f"subsection names cannot contain newlines: {name}")
|
|
84
84
|
|
|
85
85
|
def format_name(self):
|
|
86
86
|
name = self.name
|
|
87
87
|
# Escape the backslashes:
|
|
88
|
-
name = name.replace(
|
|
88
|
+
name = name.replace("\\", r"\\")
|
|
89
89
|
# Escape the quotes:
|
|
90
|
-
name = name.replace('"', r
|
|
90
|
+
name = name.replace('"', r"\"")
|
|
91
91
|
# Put in quotes:
|
|
92
92
|
return f'"{name}"'
|
|
93
93
|
|
|
94
94
|
def format(self):
|
|
95
95
|
result = (
|
|
96
|
-
f
|
|
96
|
+
f"[{Config.Section.format_name(self.section)} {self.format_name()}]\n"
|
|
97
97
|
)
|
|
98
|
-
result +=
|
|
98
|
+
result += "".join((var.format() for var in self.variables))
|
|
99
99
|
return result
|
|
100
100
|
|
|
101
101
|
class Variable:
|
|
@@ -108,16 +108,16 @@ class Config:
|
|
|
108
108
|
@staticmethod
|
|
109
109
|
def validate_name(name):
|
|
110
110
|
if not name:
|
|
111
|
-
raise RuntimeError(
|
|
111
|
+
raise RuntimeError("variable names cannot be empty")
|
|
112
112
|
for c in name:
|
|
113
|
-
if c.isalnum() or c ==
|
|
113
|
+
if c.isalnum() or c == "-":
|
|
114
114
|
continue
|
|
115
115
|
raise RuntimeError(
|
|
116
|
-
f
|
|
116
|
+
f"variable name can only contain alphanumeric characters or -: {name}"
|
|
117
117
|
)
|
|
118
118
|
if not name[0].isalnum():
|
|
119
119
|
raise RuntimeError(
|
|
120
|
-
f
|
|
120
|
+
f"variable name must start with an alphanumeric character: {name}"
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
@staticmethod
|
|
@@ -130,23 +130,23 @@ class Config:
|
|
|
130
130
|
def format_value(self):
|
|
131
131
|
value = self.value
|
|
132
132
|
# Escape the backslashes:
|
|
133
|
-
value = value.replace(
|
|
133
|
+
value = value.replace("\\", r"\\")
|
|
134
134
|
# Escape the supported escape sequences (\n, \t and \b):
|
|
135
|
-
value = value.replace(
|
|
136
|
-
value = value.replace(
|
|
137
|
-
value = value.replace(
|
|
135
|
+
value = value.replace("\n", r"\n")
|
|
136
|
+
value = value.replace("\t", r"\t")
|
|
137
|
+
value = value.replace("\b", r"\b")
|
|
138
138
|
# Escape the quotes:
|
|
139
|
-
value = value.replace('"', r
|
|
139
|
+
value = value.replace('"', r"\"")
|
|
140
140
|
# Put in quotes:
|
|
141
141
|
value = f'"{value}"'
|
|
142
142
|
return value
|
|
143
143
|
|
|
144
144
|
def format(self):
|
|
145
|
-
return f
|
|
145
|
+
return f" {self.format_name()} = {self.format_value()}\n"
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
class Git:
|
|
149
|
-
EXE =
|
|
149
|
+
EXE = "git"
|
|
150
150
|
|
|
151
151
|
@staticmethod
|
|
152
152
|
def check(*args, **kwargs):
|
|
@@ -158,7 +158,7 @@ class Git:
|
|
|
158
158
|
|
|
159
159
|
@staticmethod
|
|
160
160
|
def get_global_config():
|
|
161
|
-
return Config(os.path.expanduser(
|
|
161
|
+
return Config(os.path.expanduser("~/.gitconfig"))
|
|
162
162
|
|
|
163
163
|
@staticmethod
|
|
164
164
|
@contextmanager
|
|
@@ -169,10 +169,10 @@ class Git:
|
|
|
169
169
|
config = Git.get_global_config()
|
|
170
170
|
with utils.protected_file(config.path):
|
|
171
171
|
with config.backup() as old_contents:
|
|
172
|
-
variables = [Config.Variable(
|
|
172
|
+
variables = [Config.Variable("insteadOf", repo.clone_url)]
|
|
173
173
|
subsection = Config.Subsection(
|
|
174
|
-
|
|
174
|
+
"url", repo.clone_url_with_auth, variables
|
|
175
175
|
)
|
|
176
|
-
new_contents = f
|
|
176
|
+
new_contents = f"{old_contents}\n{subsection.format()}"
|
|
177
177
|
config.write(new_contents)
|
|
178
178
|
yield
|
|
@@ -34,13 +34,13 @@ class GitHub:
|
|
|
34
34
|
# None makes no difference.
|
|
35
35
|
kwargs = {}
|
|
36
36
|
if visibility is not None:
|
|
37
|
-
kwargs[
|
|
37
|
+
kwargs["visibility"] = visibility
|
|
38
38
|
|
|
39
39
|
try:
|
|
40
40
|
if user.name == self._username:
|
|
41
41
|
# To get private repositories, get_user() must be called
|
|
42
42
|
# without arguments:
|
|
43
|
-
return self._impl.get_user().get_repos(affiliation=
|
|
43
|
+
return self._impl.get_user().get_repos(affiliation="owner", **kwargs)
|
|
44
44
|
else:
|
|
45
45
|
return self._impl.get_user(user.name).get_repos(**kwargs)
|
|
46
46
|
except GithubException:
|
|
@@ -13,7 +13,7 @@ from cgitize.repo import Repo, Visibility
|
|
|
13
13
|
|
|
14
14
|
class GitLab:
|
|
15
15
|
def __init__(self, token):
|
|
16
|
-
self._impl = Gitlab(
|
|
16
|
+
self._impl = Gitlab("https://gitlab.com", private_token=token)
|
|
17
17
|
|
|
18
18
|
def get_repo(self, repo):
|
|
19
19
|
try:
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def update(path, success, error=None):
|
|
8
|
-
with open(path,
|
|
9
|
-
contents =
|
|
8
|
+
with open(path, "w") as file:
|
|
9
|
+
contents = ""
|
|
10
10
|
if not success:
|
|
11
11
|
contents = (
|
|
12
|
-
|
|
12
|
+
"""<p style="text-align: center; color: red; font-weight: bold;">"""
|
|
13
13
|
)
|
|
14
|
-
contents +=
|
|
14
|
+
contents += """Some repositories couldn't be updated, please check application logs for details."""
|
|
15
15
|
if error is not None:
|
|
16
|
-
contents += f
|
|
17
|
-
contents +=
|
|
16
|
+
contents += f"""<br>{type(error).__name__}: {error}"""
|
|
17
|
+
contents += """</p>\n"""
|
|
18
18
|
file.write(contents)
|