diff --git a/.github/workflows/code-mirror.yml b/.github/workflows/code-mirror.yml new file mode 100644 index 0000000..5567e08 --- /dev/null +++ b/.github/workflows/code-mirror.yml @@ -0,0 +1,37 @@ +name: Mirror source code to GitVerse + +on: + push: + branches: + - '**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: code-mirror-${{ github.ref }} + cancel-in-progress: true + +jobs: + mirror: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Mirror all branches + env: + GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }} + GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }} + GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }} + run: | + set -euo pipefail + test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; } + remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git" + git remote add gitverse "$remote" + # Populate local refs first, so a manual run mirrors every branch too. + git fetch origin '+refs/heads/*:refs/heads/*' + git push --prune --force gitverse 'refs/heads/*:refs/heads/*' diff --git a/.github/workflows/release-mirror.yml b/.github/workflows/release-mirror.yml new file mode 100644 index 0000000..80c1280 --- /dev/null +++ b/.github/workflows/release-mirror.yml @@ -0,0 +1,186 @@ +name: Mirror releases to GitVerse, SourceCraft, and GitLab + +on: + workflow_run: + workflows: + - Release + types: + - completed + workflow_dispatch: + inputs: + tag: + description: Git tag of the GitHub release to mirror (for example v0.7.1) + required: true + type: string + +permissions: + contents: read + +jobs: + mirror: + runs-on: ubuntu-latest + steps: + - name: Resolve release tag + id: release + env: + GH_TOKEN: ${{ github.token }} + MANUAL_TAG: ${{ inputs.tag }} + WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + set -euo pipefail + tag="$MANUAL_TAG" + if [ -z "$tag" ] && [[ "$WORKFLOW_HEAD_BRANCH" == v* ]]; then + tag="$WORKFLOW_HEAD_BRANCH" + fi + if [ -z "$tag" ] && [ -n "$WORKFLOW_HEAD_SHA" ]; then + releases=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ + -H 'Accept: application/vnd.github+json' \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases?per_page=100") + while IFS= read -r candidate; do + ref=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ + -H 'Accept: application/vnd.github+json' \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${candidate}") + sha=$(jq -r '.object.sha' <<< "$ref") + if [ "$(jq -r '.object.type' <<< "$ref")" = tag ]; then + sha=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ + -H 'Accept: application/vnd.github+json' \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/git/tags/${sha}" | jq -r '.object.sha') + fi + if [ "$sha" = "$WORKFLOW_HEAD_SHA" ]; then + tag="$candidate" + break + fi + done < <(jq -r '.[].tag_name' <<< "$releases") + fi + if [ -z "$tag" ] || [[ "$tag" != v* ]]; then + echo 'No release tag was found; release.yml may have failed before creating a GitHub release.' + echo 'found=false' >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo 'found=true' >> "$GITHUB_OUTPUT" + + - name: Download GitHub release metadata and assets + if: steps.release.outputs.found == 'true' + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + mkdir -p assets + curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ + -H 'Accept: application/vnd.github+json' \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" > release.json + jq -e '.tag_name == $tag' --arg tag "$TAG" release.json >/dev/null + jq -r '.assets[] | [.name, .browser_download_url] | @tsv' release.json | + while IFS=$'\t' read -r name url; do + curl -fsSL -L -H "Authorization: Bearer $GH_TOKEN" \ + -H 'Accept: application/octet-stream' -o "assets/$name" "$url" + done + + - name: Sync tag and release to GitVerse + if: steps.release.outputs.found == 'true' + env: + GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }} + GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }} + GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }} + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; } + project="${GITVERSE_ORG}%2F${GITVERSE_REPO}" + remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git" + git clone --mirror "https://github.com/${GITHUB_REPOSITORY}.git" mirror.git + git -C mirror.git remote add gitverse "$remote" + git -C mirror.git push gitverse "refs/tags/${TAG}:refs/tags/${TAG}" + + title=$(jq -r '.name // .tag_name' release.json) + notes=$(jq -r '.body // ""' release.json) + payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ + '{name:$title, tag_name:$tag, description:$notes, ref:$tag}') + curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "https://gitverse.ru/api/v4/projects/${project}/releases" || \ + curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}" + for file in assets/*; do + [ -f "$file" ] || continue + upload=$(curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -F "file=@$file" "https://gitverse.ru/api/v4/projects/${project}/uploads") + url=$(jq -r '.url // .file_path' <<< "$upload") + name=$(basename "$file") + link=$(jq -n --arg name "$name" --arg url "$url" '{name:$name,url:$url}') + curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -H 'Content-Type: application/json' --data "$link" \ + "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}/assets/links" || true + done + + - name: Sync tag and release to SourceCraft + if: steps.release.outputs.found == 'true' + env: + SOURCECRAFT_PAT: ${{ secrets.SOURCECRAFT_PAT }} + SOURCECRAFT_ORG: ${{ vars.SOURCECRAFT_ORG || 'dexoron-dev' }} + SOURCECRAFT_REPO: ${{ vars.SOURCECRAFT_REPO || 'dcr' }} + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + test -n "$SOURCECRAFT_PAT" || { echo 'SOURCECRAFT_PAT is not configured' >&2; exit 1; } + remote="https://anyname:${SOURCECRAFT_PAT}@git.sourcecraft.dev/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}.git" + git clone "$remote" sourcecraft + git -C sourcecraft remote add github "https://github.com/${GITHUB_REPOSITORY}.git" + git -C sourcecraft fetch github "refs/tags/${TAG}:refs/tags/${TAG}" + git -C sourcecraft push origin "refs/tags/${TAG}" + + title=$(jq -r '.name // .tag_name' release.json) + notes=$(jq -r '.body // ""' release.json) + payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ + '{tag:$tag,title:$title,release_notes:$notes,publish:true}') + curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}/releases" || \ + echo 'SourceCraft release already exists; continuing with assets.' + for file in assets/*; do + [ -f "$file" ] || continue + curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \ + -F "name=$(basename "$file")" -F "file=@$file" \ + "https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}/releases/tag/${TAG}/attachments" || true + done + + - name: Sync release to GitLab + if: steps.release.outputs.found == 'true' + env: + GITLAB_PAT: ${{ secrets.GITLAB_PAT }} + GITLAB_PROJECT: ${{ vars.GITLAB_PROJECT || 'dexoron-dev/dcr-tool/dcr' }} + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + test -n "$GITLAB_PAT" || { echo 'GITLAB_PAT is not configured' >&2; exit 1; } + project_id="${GITLAB_PROJECT//\//%2F}" + api="https://gitlab.com/api/v4/projects/${project_id}" + title=$(jq -r '.name // .tag_name' release.json) + notes=$(jq -r '.body // ""' release.json) + payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ + '{tag_name:$tag, name:$title, description:$notes}') + + # Tags are already mirrored by GitLab's built-in repository mirror. + curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "${api}/releases" || \ + curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "${api}/releases/${TAG}" + + for file in assets/*; do + [ -f "$file" ] || continue + upload=$(curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + -F "file=@$file" "${api}/uploads") + url="https://gitlab.com/${GITLAB_PROJECT}$(jq -r '.url' <<< "$upload")" + name=$(basename "$file") + link=$(jq -n --arg name "$name" --arg url "$url" \ + '{name:$name, url:$url, link_type:"other"}') + curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + -H 'Content-Type: application/json' --data "$link" \ + "${api}/releases/${TAG}/assets/links" || true + done diff --git a/.gitignore b/.gitignore index d0a4f5f..8c9d461 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ sandbox/** personal/ .* -!.gitignore \ No newline at end of file +!.gitignore +!.github/ \ No newline at end of file diff --git a/README.md b/README.md index a73181c..785e694 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,30 @@ Full docs at **[dcr.dexoron.su](https://dcr.dexoron.su)** or in the [`docs/`](do --- +## Repository Mirrors + +The primary repository is hosted on GitHub. The following mirrors are available for source code and releases: + +| Region | Mirror | Contents | +|---|---|---| +| Russia | [GitVerse](https://gitverse.ru/dcr_labs/dcr) **(recommended)** | Source code and releases | +| Russia | [SourceCraft](https://sourcecraft.dev/dexoron-dev/dcr) | Source code and releases | +| Worldwide | [SourceForge](https://sourceforge.net/projects/dcr-tool/) **(recommended)** | Source code and releases | +| Worldwide | [GitLab](https://gitlab.com/dexoron-dev/dcr-tool/dcr) | Source code and releases | + +--- + +## VS Code Extension + +Install the recommended [DCR extension by Dexoron-Labs](https://marketplace.visualstudio.com/items?itemName=Dexoron-Labs.dcr) for VS Code. Do not install Microsoft C/C++ extensions for DCR projects; DCR uses `clangd` instead. If you need debugging with Microsoft's debugger, use MS C/C++ only for its debugger component. + +Recommended companion extensions: + +- [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) +- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), or MS C/C++ for debugger support only + +--- + ## Contributors