repligit 0.2.0__tar.gz → 0.4.0__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.
- {repligit-0.2.0 → repligit-0.4.0}/.envrc +1 -1
- {repligit-0.2.0 → repligit-0.4.0}/.github/dependabot.yml +6 -2
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/ci.yml +2 -2
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/coverage.yml +6 -6
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/label.yml +1 -1
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/publish.yml +6 -5
- repligit-0.4.0/.github/workflows/requirements/publish.txt +3 -0
- repligit-0.4.0/.github/workflows/requirements/security.txt +1 -0
- repligit-0.4.0/.github/workflows/requirements/style.txt +3 -0
- repligit-0.4.0/.github/workflows/requirements/unit-tests.txt +6 -0
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/security.yml +6 -6
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/style.yml +11 -3
- {repligit-0.2.0 → repligit-0.4.0}/.github/workflows/unit-tests.yml +4 -4
- {repligit-0.2.0 → repligit-0.4.0}/PKG-INFO +11 -5
- {repligit-0.2.0 → repligit-0.4.0}/README.md +2 -3
- {repligit-0.2.0 → repligit-0.4.0}/examples/git_mirror.py +5 -1
- {repligit-0.2.0 → repligit-0.4.0}/examples/git_mirror_async.py +5 -1
- {repligit-0.2.0 → repligit-0.4.0}/pyproject.toml +10 -3
- {repligit-0.2.0 → repligit-0.4.0}/spack/repos/spack_repo/repligit/packages/py_repligit/package.py +1 -0
- {repligit-0.2.0/spack → repligit-0.4.0}/spack.yaml +6 -4
- {repligit-0.2.0 → repligit-0.4.0}/src/repligit/__init__.py +1 -1
- {repligit-0.2.0 → repligit-0.4.0}/src/repligit/asyncio/__init__.py +1 -1
- repligit-0.4.0/src/repligit/asyncio/client.py +130 -0
- repligit-0.4.0/src/repligit/asyncio/parse.py +88 -0
- repligit-0.4.0/src/repligit/client.py +168 -0
- repligit-0.4.0/src/repligit/exceptions.py +18 -0
- repligit-0.4.0/src/repligit/parse.py +172 -0
- repligit-0.4.0/tests/test_client.py +217 -0
- repligit-0.2.0/.github/workflows/requirements/publish.txt +0 -2
- repligit-0.2.0/.github/workflows/requirements/security.txt +0 -1
- repligit-0.2.0/.github/workflows/requirements/style.txt +0 -2
- repligit-0.2.0/.github/workflows/requirements/unit-tests.txt +0 -5
- repligit-0.2.0/src/repligit/asyncio/client.py +0 -95
- repligit-0.2.0/src/repligit/asyncio/parse.py +0 -51
- repligit-0.2.0/src/repligit/client.py +0 -114
- repligit-0.2.0/src/repligit/parse.py +0 -103
- repligit-0.2.0/tests/test_client.py +0 -63
- {repligit-0.2.0 → repligit-0.4.0}/.github/ISSUE_TEMPLATE/issue-feature-request.md +0 -0
- {repligit-0.2.0 → repligit-0.4.0}/.github/labeler.yml +0 -0
- {repligit-0.2.0 → repligit-0.4.0}/.gitignore +0 -0
- {repligit-0.2.0 → repligit-0.4.0}/LICENSE +0 -0
- {repligit-0.2.0 → repligit-0.4.0}/spack/repos/spack_repo/repligit/repo.yaml +0 -0
- {repligit-0.2.0 → repligit-0.4.0}/tests/__init__.py +0 -0
|
@@ -3,9 +3,13 @@ updates:
|
|
|
3
3
|
- package-ecosystem: "github-actions"
|
|
4
4
|
directory: "/"
|
|
5
5
|
schedule:
|
|
6
|
-
interval: "
|
|
6
|
+
interval: "monthly"
|
|
7
|
+
groups:
|
|
8
|
+
codeql:
|
|
9
|
+
patterns:
|
|
10
|
+
- "github/codeql-action*"
|
|
7
11
|
|
|
8
12
|
- package-ecosystem: "pip"
|
|
9
13
|
directory: "/.github/workflows/requirements"
|
|
10
14
|
schedule:
|
|
11
|
-
interval: "
|
|
15
|
+
interval: "monthly"
|
|
@@ -22,13 +22,13 @@ jobs:
|
|
|
22
22
|
security: ${{ steps.filter.outputs.security }}
|
|
23
23
|
unit-tests: ${{ steps.filter.outputs.unit-tests }}
|
|
24
24
|
steps:
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # @v2
|
|
26
26
|
if: ${{ github.event_name == 'push' }}
|
|
27
27
|
with:
|
|
28
28
|
fetch-depth: 0
|
|
29
29
|
|
|
30
30
|
# For pull requests it's not necessary to checkout the code
|
|
31
|
-
- uses: dorny/paths-filter@
|
|
31
|
+
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706
|
|
32
32
|
id: filter
|
|
33
33
|
with:
|
|
34
34
|
filters: |
|
|
@@ -13,10 +13,10 @@ jobs:
|
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
15
|
- name: checkout PR branch
|
|
16
|
-
uses: actions/checkout@
|
|
16
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
17
17
|
with:
|
|
18
18
|
fetch-depth: 2 # https://stackoverflow.com/a/74268200
|
|
19
|
-
- uses: actions/setup-python@
|
|
19
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
|
20
20
|
with:
|
|
21
21
|
python-version: '3.13'
|
|
22
22
|
cache: 'pip'
|
|
@@ -26,14 +26,14 @@ jobs:
|
|
|
26
26
|
|
|
27
27
|
- name: Install Python dependencies
|
|
28
28
|
run: |
|
|
29
|
-
pip install .
|
|
29
|
+
pip install .[aiohttp]
|
|
30
30
|
pip install -r .github/workflows/requirements/unit-tests.txt
|
|
31
31
|
|
|
32
32
|
- name: Download PR coverage artifact
|
|
33
33
|
if: github.event_name == 'pull_request'
|
|
34
34
|
id: download_pr_cov
|
|
35
35
|
continue-on-error: true
|
|
36
|
-
uses: actions/download-artifact@
|
|
36
|
+
uses: actions/download-artifact@v8.0.1
|
|
37
37
|
with:
|
|
38
38
|
name: pr-coverage-${{ github.sha }}
|
|
39
39
|
|
|
@@ -64,7 +64,7 @@ jobs:
|
|
|
64
64
|
if: github.event_name == 'pull_request'
|
|
65
65
|
id: download_base
|
|
66
66
|
continue-on-error: true
|
|
67
|
-
uses: actions/download-artifact@
|
|
67
|
+
uses: actions/download-artifact@v8.0.1
|
|
68
68
|
with:
|
|
69
69
|
name: base-coverage-${{ env.MERGE_BASE_COMMIT }}
|
|
70
70
|
|
|
@@ -110,7 +110,7 @@ jobs:
|
|
|
110
110
|
|
|
111
111
|
- name: Upload base coverage artifact
|
|
112
112
|
if: github.ref_name == 'main' && github.event_name == 'push'
|
|
113
|
-
uses: actions/upload-artifact@
|
|
113
|
+
uses: actions/upload-artifact@v7
|
|
114
114
|
with:
|
|
115
115
|
name: base-coverage-${{ github.sha }}
|
|
116
116
|
path: base_coverage.json
|
|
@@ -7,12 +7,13 @@ jobs:
|
|
|
7
7
|
build:
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
steps:
|
|
10
|
-
- uses: actions/checkout@
|
|
10
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
11
11
|
with:
|
|
12
12
|
persist-credentials: false
|
|
13
|
+
fetch-depth: 0
|
|
13
14
|
|
|
14
15
|
- name: Set up Python
|
|
15
|
-
uses: actions/setup-python@
|
|
16
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
|
16
17
|
with:
|
|
17
18
|
cache: 'pip'
|
|
18
19
|
cache-dependency-path: '.github/workflows/requirements/publish.txt'
|
|
@@ -26,7 +27,7 @@ jobs:
|
|
|
26
27
|
python3 -m build
|
|
27
28
|
|
|
28
29
|
- name: Save distribution files as artifact
|
|
29
|
-
uses: actions/upload-artifact@
|
|
30
|
+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
|
30
31
|
with:
|
|
31
32
|
name: python-package-distributions
|
|
32
33
|
path: dist/
|
|
@@ -41,10 +42,10 @@ jobs:
|
|
|
41
42
|
id-token: write
|
|
42
43
|
steps:
|
|
43
44
|
- name: Download distribution files from artifact
|
|
44
|
-
uses: actions/download-artifact@
|
|
45
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
|
45
46
|
with:
|
|
46
47
|
name: python-package-distributions
|
|
47
48
|
path: dist/
|
|
48
49
|
|
|
49
50
|
- name: Publish package to PyPI
|
|
50
|
-
uses: pypa/gh-action-pypi-publish@
|
|
51
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bandit[sarif,toml]==1.9.4
|
|
@@ -10,11 +10,11 @@ jobs:
|
|
|
10
10
|
bandit-scan:
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
|
-
- uses: actions/checkout@
|
|
13
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
14
14
|
|
|
15
15
|
# https://bandit.readthedocs.io/en/latest/faq.html#under-which-version-of-python-should-i-install-bandit
|
|
16
16
|
- name: Set up Python 3.10
|
|
17
|
-
uses: actions/setup-python@
|
|
17
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
|
18
18
|
with:
|
|
19
19
|
python-version: '3.10'
|
|
20
20
|
cache: 'pip'
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
# the PR if any issues are found
|
|
31
31
|
- name: Upload SARIF file
|
|
32
32
|
if: always()
|
|
33
|
-
uses: github/codeql-action/upload-sarif@
|
|
33
|
+
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81
|
|
34
34
|
with:
|
|
35
35
|
sarif_file: results.sarif
|
|
36
36
|
category: bandit
|
|
@@ -43,15 +43,15 @@ jobs:
|
|
|
43
43
|
language: [actions, python]
|
|
44
44
|
steps:
|
|
45
45
|
- name: Checkout repository
|
|
46
|
-
uses: actions/checkout@
|
|
46
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
47
47
|
|
|
48
48
|
- name: Initialize CodeQL
|
|
49
|
-
uses: github/codeql-action/init@
|
|
49
|
+
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81
|
|
50
50
|
with:
|
|
51
51
|
languages: ${{ matrix.language }}
|
|
52
52
|
build-mode: none
|
|
53
53
|
|
|
54
54
|
- name: Perform CodeQL Analysis
|
|
55
|
-
uses: github/codeql-action/analyze@
|
|
55
|
+
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81
|
|
56
56
|
with:
|
|
57
57
|
category: "/language:${{matrix.language}}"
|
|
@@ -9,18 +9,23 @@ jobs:
|
|
|
9
9
|
lint:
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
steps:
|
|
12
|
-
- uses: actions/checkout@
|
|
12
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
13
13
|
|
|
14
14
|
- name: Set up Python 3.10
|
|
15
|
-
uses: actions/setup-python@
|
|
15
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
|
16
16
|
with:
|
|
17
17
|
python-version: '3.10'
|
|
18
18
|
cache: 'pip'
|
|
19
|
-
cache-dependency-path:
|
|
19
|
+
cache-dependency-path: |
|
|
20
|
+
pyproject.toml
|
|
21
|
+
.github/workflows/requirements/unit-tests.txt
|
|
22
|
+
.github/workflows/requirements/style.txt
|
|
20
23
|
|
|
21
24
|
- name: Install Python dependencies
|
|
22
25
|
run: |
|
|
26
|
+
pip install -r .github/workflows/requirements/unit-tests.txt
|
|
23
27
|
pip install -r .github/workflows/requirements/style.txt
|
|
28
|
+
pip install -e .[aiohttp]
|
|
24
29
|
|
|
25
30
|
- name: Run Ruff
|
|
26
31
|
run: |
|
|
@@ -28,5 +33,8 @@ jobs:
|
|
|
28
33
|
ruff check --select I --diff
|
|
29
34
|
ruff format --check --diff
|
|
30
35
|
|
|
36
|
+
- name: Run Type Checker
|
|
37
|
+
run: ty check
|
|
38
|
+
|
|
31
39
|
- name: Run Typos
|
|
32
40
|
run: typos
|
|
@@ -12,8 +12,8 @@ jobs:
|
|
|
12
12
|
matrix:
|
|
13
13
|
python-version: ['3.10', '3.13']
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: actions/setup-python@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
16
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
|
17
17
|
with:
|
|
18
18
|
python-version: ${{ matrix.python-version }}
|
|
19
19
|
cache: 'pip'
|
|
@@ -23,8 +23,8 @@ jobs:
|
|
|
23
23
|
|
|
24
24
|
- name: Install Python dependencies
|
|
25
25
|
run: |
|
|
26
|
-
pip install .
|
|
27
26
|
pip install -r .github/workflows/requirements/unit-tests.txt
|
|
27
|
+
pip install -e .[aiohttp]
|
|
28
28
|
|
|
29
29
|
- name: Run Unit Tests (coverage on 3.13)
|
|
30
30
|
run: |
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
- name: Upload coverage artifact
|
|
42
42
|
# this is needed so we can compare diffs in coverage.yml
|
|
43
43
|
if: ${{ matrix.python-version == '3.13' }}
|
|
44
|
-
uses: actions/upload-artifact@
|
|
44
|
+
uses: actions/upload-artifact@v7
|
|
45
45
|
with:
|
|
46
46
|
name: pr-coverage-${{ github.sha }}
|
|
47
47
|
path: pr_coverage.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: repligit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A python library implementing the git http transfer protocol.
|
|
5
5
|
Project-URL: Homepage, https://github.com/llnl/repligit
|
|
6
6
|
Project-URL: Issues, https://github.com/llnl/repligit/issues
|
|
@@ -12,6 +12,13 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Provides-Extra: aiohttp
|
|
14
14
|
Requires-Dist: aiohttp; extra == 'aiohttp'
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-mock; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
20
|
+
Requires-Dist: ty; extra == 'dev'
|
|
21
|
+
Requires-Dist: typos; extra == 'dev'
|
|
15
22
|
Description-Content-Type: text/markdown
|
|
16
23
|
|
|
17
24
|
# repligit
|
|
@@ -57,7 +64,7 @@ def main():
|
|
|
57
64
|
gl_refs = ls_remote(
|
|
58
65
|
dest_remote_url,
|
|
59
66
|
username=dest_username,
|
|
60
|
-
password=dest_password
|
|
67
|
+
password=dest_password,
|
|
61
68
|
)
|
|
62
69
|
|
|
63
70
|
want_sha = gh_refs[target_ref]
|
|
@@ -65,7 +72,7 @@ def main():
|
|
|
65
72
|
|
|
66
73
|
from_sha = gl_refs.get(target_ref) or ("0" * 40)
|
|
67
74
|
|
|
68
|
-
if
|
|
75
|
+
if from_sha == want_sha:
|
|
69
76
|
print("Everything is up to date")
|
|
70
77
|
return
|
|
71
78
|
|
|
@@ -92,7 +99,6 @@ def main():
|
|
|
92
99
|
|
|
93
100
|
if __name__ == "__main__":
|
|
94
101
|
main()
|
|
95
|
-
|
|
96
102
|
```
|
|
97
103
|
|
|
98
104
|
## License
|
|
@@ -41,7 +41,7 @@ def main():
|
|
|
41
41
|
gl_refs = ls_remote(
|
|
42
42
|
dest_remote_url,
|
|
43
43
|
username=dest_username,
|
|
44
|
-
password=dest_password
|
|
44
|
+
password=dest_password,
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
want_sha = gh_refs[target_ref]
|
|
@@ -49,7 +49,7 @@ def main():
|
|
|
49
49
|
|
|
50
50
|
from_sha = gl_refs.get(target_ref) or ("0" * 40)
|
|
51
51
|
|
|
52
|
-
if
|
|
52
|
+
if from_sha == want_sha:
|
|
53
53
|
print("Everything is up to date")
|
|
54
54
|
return
|
|
55
55
|
|
|
@@ -76,7 +76,6 @@ def main():
|
|
|
76
76
|
|
|
77
77
|
if __name__ == "__main__":
|
|
78
78
|
main()
|
|
79
|
-
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
## License
|
|
@@ -27,7 +27,7 @@ def main():
|
|
|
27
27
|
|
|
28
28
|
from_sha = gl_refs.get(target_ref) or ("0" * 40)
|
|
29
29
|
|
|
30
|
-
if
|
|
30
|
+
if from_sha == want_sha:
|
|
31
31
|
print("Everything is up to date")
|
|
32
32
|
return
|
|
33
33
|
|
|
@@ -40,6 +40,10 @@ def main():
|
|
|
40
40
|
# password=src_password, # Uncomment if source repo requires auth
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
+
if packfile is None:
|
|
44
|
+
print("Failed to fetch packfile")
|
|
45
|
+
return
|
|
46
|
+
|
|
43
47
|
# Upload packfile to destination repository
|
|
44
48
|
send_pack(
|
|
45
49
|
dest_remote_url,
|
|
@@ -31,7 +31,7 @@ async def main():
|
|
|
31
31
|
|
|
32
32
|
from_sha = gl_refs.get(target_ref) or ("0" * 40)
|
|
33
33
|
|
|
34
|
-
if
|
|
34
|
+
if from_sha == want_sha:
|
|
35
35
|
print("Everything is up to date")
|
|
36
36
|
return
|
|
37
37
|
|
|
@@ -44,6 +44,10 @@ async def main():
|
|
|
44
44
|
# password=src_password, # Uncomment if source repo requires auth
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
+
if packfile is None:
|
|
48
|
+
print("Failed to fetch packfile")
|
|
49
|
+
return
|
|
50
|
+
|
|
47
51
|
# Upload packfile to destination repository
|
|
48
52
|
await send_pack(
|
|
49
53
|
dest_remote_url,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "repligit"
|
|
7
|
-
|
|
8
|
-
dependencies = []
|
|
7
|
+
dynamic = ["version"]
|
|
9
8
|
authors = [
|
|
10
9
|
{ name="Alec Scott" },
|
|
11
10
|
{ name="Caetano Melone"},
|
|
@@ -25,6 +24,10 @@ Issues = "https://github.com/llnl/repligit/issues"
|
|
|
25
24
|
|
|
26
25
|
[project.optional-dependencies]
|
|
27
26
|
aiohttp = ["aiohttp"]
|
|
27
|
+
dev = ["ty", "ruff", "typos", "coverage", "pytest", "pytest-mock"]
|
|
28
|
+
|
|
29
|
+
[tool.hatch.version]
|
|
30
|
+
source = "vcs"
|
|
28
31
|
|
|
29
32
|
[tool.pytest.ini_options]
|
|
30
33
|
pythonpath = [
|
|
@@ -39,3 +42,7 @@ exclude = ["spack"]
|
|
|
39
42
|
exclude_dirs = []
|
|
40
43
|
# allow use of assert
|
|
41
44
|
skips = ["B101"]
|
|
45
|
+
|
|
46
|
+
[tool.ty.src]
|
|
47
|
+
# exclude spack directory from type checking
|
|
48
|
+
exclude = ["spack/**"]
|
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
# It describes a set of packages to be installed, along with
|
|
4
4
|
# configuration settings.
|
|
5
5
|
spack:
|
|
6
|
-
# add package specs to the `specs` list
|
|
7
6
|
repos:
|
|
8
|
-
|
|
7
|
+
repligit: spack/repos/spack_repo/repligit
|
|
9
8
|
specs:
|
|
10
9
|
- py-codespell
|
|
11
10
|
- py-pytest
|
|
12
|
-
- py-repligit
|
|
11
|
+
- py-repligit +aiohttp
|
|
13
12
|
- py-bandit
|
|
14
13
|
- py-ruff
|
|
15
14
|
- py-pip
|
|
15
|
+
- py-ty
|
|
16
|
+
- py-build
|
|
17
|
+
- py-hatch-vcs
|
|
16
18
|
view: true
|
|
17
19
|
concretizer:
|
|
18
20
|
unify: true
|
|
19
21
|
develop:
|
|
20
22
|
py-repligit:
|
|
21
23
|
spec: py-repligit@=main
|
|
22
|
-
path:
|
|
24
|
+
path: .
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
5
|
+
from repligit.asyncio.parse import read_pkt_lines
|
|
6
|
+
from repligit.exceptions import (
|
|
7
|
+
RefUpdateRejected,
|
|
8
|
+
RemoteError,
|
|
9
|
+
UnexpectedResponse,
|
|
10
|
+
UnpackFailed,
|
|
11
|
+
)
|
|
12
|
+
from repligit.parse import generate_fetch_pack_request, generate_send_pack_header
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def ls_remote(
|
|
16
|
+
url: str, username: str | None = None, password: str | None = None
|
|
17
|
+
) -> dict[str, str]:
|
|
18
|
+
"""Get commit hash of remote master branch, return SHA-1 hex string or
|
|
19
|
+
None if no remote commits.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
url = f"{url}/info/refs?service=git-upload-pack"
|
|
23
|
+
auth = aiohttp.BasicAuth(username or "", password) if password else None
|
|
24
|
+
async with aiohttp.ClientSession(auth=auth) as session:
|
|
25
|
+
async with session.get(url, raise_for_status=True) as resp:
|
|
26
|
+
lines = read_pkt_lines(resp.content)
|
|
27
|
+
service_line = await anext(lines)
|
|
28
|
+
if service_line != "# service=git-upload-pack":
|
|
29
|
+
raise UnexpectedResponse(f"invalid service line: {service_line!r}")
|
|
30
|
+
|
|
31
|
+
refs: dict[str, str] = {}
|
|
32
|
+
async for line in lines:
|
|
33
|
+
# The first ref line carries the server capabilities after a NUL byte.
|
|
34
|
+
sha, ref = line.split("\x00", 1)[0].split()
|
|
35
|
+
refs[ref] = sha
|
|
36
|
+
return refs
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def fetch_pack(
|
|
40
|
+
url: str,
|
|
41
|
+
want_sha: str,
|
|
42
|
+
have_shas: Iterable[str],
|
|
43
|
+
username: str | None = None,
|
|
44
|
+
password: str | None = None,
|
|
45
|
+
) -> bytes | None:
|
|
46
|
+
"""Download a packfile from a remote server."""
|
|
47
|
+
# ensure have_shas is a set, else packfile errors will occur
|
|
48
|
+
have_shas = set(have_shas)
|
|
49
|
+
|
|
50
|
+
url = f"{url}/git-upload-pack"
|
|
51
|
+
auth = aiohttp.BasicAuth(username or "", password) if password else None
|
|
52
|
+
|
|
53
|
+
request = generate_fetch_pack_request(want_sha, have_shas)
|
|
54
|
+
|
|
55
|
+
async with aiohttp.ClientSession(auth=auth) as session:
|
|
56
|
+
async with session.post(
|
|
57
|
+
url,
|
|
58
|
+
headers={
|
|
59
|
+
"Content-type": "application/x-git-upload-pack-request",
|
|
60
|
+
},
|
|
61
|
+
data=request,
|
|
62
|
+
raise_for_status=True,
|
|
63
|
+
timeout=None,
|
|
64
|
+
) as resp:
|
|
65
|
+
# Consume negotiation pkt-lines. With multi-ack the server may
|
|
66
|
+
# send several "ACK <sha> continue|common|ready" lines before the
|
|
67
|
+
# terminating "NAK" or final "ACK <sha>", then the packfile.
|
|
68
|
+
while True:
|
|
69
|
+
length_bytes = await resp.content.readexactly(4)
|
|
70
|
+
line_length = int(length_bytes, 16)
|
|
71
|
+
|
|
72
|
+
line = await resp.content.readexactly(line_length - 4)
|
|
73
|
+
|
|
74
|
+
prefix = line[:3]
|
|
75
|
+
|
|
76
|
+
# e.g. "ERR upload-pack: not our ref <sha>"
|
|
77
|
+
if prefix == b"ERR":
|
|
78
|
+
raise RemoteError(line.decode("utf-8").strip())
|
|
79
|
+
|
|
80
|
+
if prefix not in (b"NAK", b"ACK"):
|
|
81
|
+
return None
|
|
82
|
+
if prefix == b"NAK" or len(line.split()) == 2:
|
|
83
|
+
# "NAK" or final "ACK <sha>": negotiation done, packfile
|
|
84
|
+
# follows. Unlike the sync API, the body must be read
|
|
85
|
+
# within this context to be usable by the caller.
|
|
86
|
+
return await resp.content.read()
|
|
87
|
+
# intermediate multi-ack line: "ACK <sha> continue|common|ready"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def send_pack(
|
|
91
|
+
url: str,
|
|
92
|
+
ref: str,
|
|
93
|
+
from_sha: str,
|
|
94
|
+
to_sha: str,
|
|
95
|
+
packfile: bytes,
|
|
96
|
+
username: str | None = None,
|
|
97
|
+
password: str | None = None,
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Send a packfile to a remote server."""
|
|
100
|
+
url = f"{url}/git-receive-pack"
|
|
101
|
+
auth = aiohttp.BasicAuth(username or "", password) if password else None
|
|
102
|
+
|
|
103
|
+
header = generate_send_pack_header(ref, from_sha, to_sha)
|
|
104
|
+
# unlike in the sync version the packfile is already read into memory
|
|
105
|
+
receive_pack_request = header + packfile
|
|
106
|
+
|
|
107
|
+
async with aiohttp.ClientSession(auth=auth) as session:
|
|
108
|
+
async with session.post(
|
|
109
|
+
url,
|
|
110
|
+
headers={
|
|
111
|
+
"Content-type": "application/x-git-receive-pack-request",
|
|
112
|
+
},
|
|
113
|
+
data=receive_pack_request,
|
|
114
|
+
raise_for_status=True,
|
|
115
|
+
) as resp:
|
|
116
|
+
lines = read_pkt_lines(resp.content)
|
|
117
|
+
unpack_status = await anext(lines)
|
|
118
|
+
if unpack_status != "unpack ok":
|
|
119
|
+
raise UnpackFailed(unpack_status)
|
|
120
|
+
|
|
121
|
+
# "ng <ref> <reason>" (ng = not good) means the remote rejected the
|
|
122
|
+
# update. The reason may be non-fast-forward, hook declined, etc.
|
|
123
|
+
ref_status = await anext(lines)
|
|
124
|
+
prefix = f"ng {ref} "
|
|
125
|
+
if ref_status.startswith(prefix):
|
|
126
|
+
reason_str = ref_status[len(prefix) :]
|
|
127
|
+
raise RefUpdateRejected(reason_str)
|
|
128
|
+
|
|
129
|
+
if ref_status != f"ok {ref}":
|
|
130
|
+
raise UnexpectedResponse(f"unexpected ref status line: {ref_status!r}")
|