From 9ab712dc2f8525593e027be0191bbc06f4c3807a Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Tue, 24 Mar 2026 02:46:10 +0800 Subject: [PATCH 01/11] Implement beta publishing on development branch --- .github/workflows/bump-version.yml | 2 +- .github/workflows/publish.yml | 174 +++++++++++++++++++++-------- 2 files changed, 126 insertions(+), 50 deletions(-) diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index f4b9a88..bf9332b 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -3,7 +3,7 @@ name: Bump version tag on merge on: pull_request_target: types: [closed] - branches: [main] + branches: [main, development] permissions: contents: write diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 02fb3d1..d948be5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -39,7 +39,6 @@ jobs: env: ARCHITECTURE: ${{ matrix.arch }} VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} - FILENAME: gitmastery-${{ needs.prepare.outputs.version_number }}-linux-${{ matrix.arch }} REF_NAME: ${{ needs.prepare.outputs.ref_name }} steps: @@ -58,21 +57,28 @@ jobs: - name: Build binary run: | echo "__version__ = \"$REF_NAME\"" > app/version.py + if [[ "$REF_NAME" == *"-beta"* ]]; then + FILENAME="gitmastery-beta-${VERSION_NUMBER}-linux-${ARCHITECTURE}" + else + FILENAME="gitmastery-${VERSION_NUMBER}-linux-${ARCHITECTURE}" + fi + echo "filename=$FILENAME" >> $GITHUB_OUTPUT uv run pyinstaller --onefile main.py --name $FILENAME - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/${{ env.FILENAME }} + files: dist/${{ steps.build.outputs.filename }} tag_name: ${{ env.REF_NAME }} + prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish package as artifact uses: actions/upload-artifact@v7 with: - name: ${{ env.FILENAME }} - path: dist/${{ env.FILENAME }} + name: ${{ steps.build.outputs.filename }} + path: dist/${{ steps.build.outputs.filename }} debian-build: # We support both ARM64 and AMD64 since Debian comes with support for @@ -114,50 +120,60 @@ jobs: run: | sudo apt-get install devscripts build-essential debhelper-compat + - name: Set package name + id: pkg + run: | + if [[ "${{ needs.prepare.outputs.ref_name }}" == *"-beta"* ]]; then + echo "prefix=gitmastery-beta" >> $GITHUB_OUTPUT + else + echo "prefix=gitmastery" >> $GITHUB_OUTPUT + fi + - name: Create folder structure for ${{ env.ARCHITECTURE }} distribution run: | - mkdir gitmastery-${VERSION}-${ARCHITECTURE} + mkdir ${{ steps.pkg.outputs.prefix }}-${VERSION}-${ARCHITECTURE} - name: Download ${{ env.ARCHITECTURE }} binaries from artifacts uses: actions/download-artifact@v8 with: - name: gitmastery-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }} - path: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/ + name: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }} + path: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/ - name: Create upstream tarball .orig.tar.gz run: | # Create .orig.tar.gz file - tar -czf gitmastery_${VERSION}.orig.tar.gz gitmastery-${VERSION}-${ARCHITECTURE}/gitmastery-${VERSION}-linux-${ARCHITECTURE} + tar -czf ${{ steps.pkg.outputs.prefix }}_${VERSION}.orig.tar.gz ${{ steps.pkg.outputs.prefix }}-${VERSION}-${ARCHITECTURE}/${{ steps.pkg.outputs.prefix }}-${VERSION}-linux-${ARCHITECTURE} - name: Generate Debian packaging files - working-directory: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }} + working-directory: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-${{ env.ARCHITECTURE }} # TODO: Update to something agnostic env: EMAIL: woojiahao1234@gmail.com NAME: Jiahao, Woo + PKG: ${{ steps.pkg.outputs.prefix }} run: | - file gitmastery-${VERSION}-linux-${ARCHITECTURE} + file ${PKG}-${VERSION}-linux-${ARCHITECTURE} # Create the debian folder mkdir debian # Generate the changelog # TODO: Maybe detect if major version change, then make it urgent - dch --create -v ${VERSION}-1 -u low --package gitmastery "$CHANGELOG_MESSAGE" + dch --create -v ${VERSION}-1 -u low --package ${PKG} "$CHANGELOG_MESSAGE" # Create the control file # TODO: Maybe detect if major version change, then make it mandatory - echo """Source: gitmastery + echo """Source: ${PKG} Maintainer: $NAME <$EMAIL> Section: misc Priority: optional Standards-Version: 4.7.0 Build-Depends: debhelper-compat (= 13) - Package: gitmastery + Package: ${PKG} Architecture: ${ARCHITECTURE} Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 (>= 2.35), python3 Description: execute Git-Mastery - gitmastery is a Git learning tool built by the National University of Singapore School of Computing + ${PKG} is a Git learning tool built by the National University of Singapore School of Computing """ > debian/control # Copy over the MIT license from the main app to this release @@ -175,14 +191,14 @@ jobs: \tdh \$@ \n override_dh_auto_install: - \tinstall -D -m 0755 gitmastery-${VERSION}-linux-${ARCHITECTURE} debian/gitmastery/usr/bin/gitmastery + \tinstall -D -m 0755 ${PKG}-${VERSION}-linux-${ARCHITECTURE} debian/${PKG}/usr/bin/${PKG} """ > debian/rules echo """usr/bin - """ > debian/gitmastery.dirs + """ > debian/${PKG}.dirs mkdir -p debian/source - echo """gitmastery-${VERSION}-linux-${ARCHITECTURE} + echo """${PKG}-${VERSION}-linux-${ARCHITECTURE} """ > debian/source/include-binaries cat debian/rules @@ -193,14 +209,15 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: gitmastery_${{ env.VERSION }}-1_${{ env.ARCHITECTURE }}.deb + files: ${{ steps.pkg.outputs.prefix }}_${{ env.VERSION }}-1_${{ env.ARCHITECTURE }}.deb tag_name: ${{ needs.prepare.outputs.ref_name }} + prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - debian-publish-apt: + debian-publish-apt-stable: needs: [prepare, debian-build] - if: needs.prepare.outputs.should_publish == 'true' + if: needs.prepare.outputs.should_publish == 'true' && !contains(needs.prepare.outputs.ref_name, 'beta') permissions: write-all uses: git-mastery/gitmastery-apt-repo/.github/workflows/debian-apt-repo.yml@main @@ -208,6 +225,16 @@ jobs: version: ${{ needs.prepare.outputs.ref_name }} secrets: inherit + debian-publish-apt-beta: + needs: [prepare, debian-build] + if: needs.prepare.outputs.should_publish == 'true' && contains(needs.prepare.outputs.ref_name, 'beta') + + permissions: write-all + uses: git-mastery/gitmastery-beta-apt-repo/.github/workflows/debian-apt-repo-beta.yml@main + with: + version: ${{ needs.prepare.outputs.ref_name }} + secrets: inherit + arch-build: needs: prepare if: needs.prepare.outputs.should_publish == 'true' @@ -217,7 +244,6 @@ jobs: env: ARCHITECTURE: amd64 VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} - FILENAME: gitmastery-${{ needs.prepare.outputs.version_number }}-arch-amd64 REF_NAME: ${{ needs.prepare.outputs.ref_name }} steps: @@ -228,6 +254,13 @@ jobs: run: | echo "__version__ = \"$REF_NAME\"" > app/version.py + if [[ "$REF_NAME" == *"-beta"* ]]; then + FILENAME="gitmastery-beta-${VERSION_NUMBER}-arch-amd64" + else + FILENAME="gitmastery-${VERSION_NUMBER}-arch-amd64" + fi + echo "filename=$FILENAME" >> $GITHUB_OUTPUT + docker run --rm \ -v $PWD:/pkg \ archlinux:base-devel \ @@ -246,16 +279,17 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/${{ env.FILENAME }} + files: dist/${{ steps.build.outputs.filename }} tag_name: ${{ env.REF_NAME }} + prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish package as artifact uses: actions/upload-artifact@v7 with: - name: ${{ env.FILENAME }} - path: dist/${{ env.FILENAME }} + name: ${{ steps.build.outputs.filename }} + path: dist/${{ steps.build.outputs.filename }} arch-publish: # Since Arch linux currently only supports x86_64 out of the box, we will focus @@ -300,24 +334,34 @@ jobs: git config --global user.email "woojiahao1234@gmail.com" git config --global init.defaultBranch master + - name: Select AUR package + run: | + if [[ "$REF_NAME" == *"-beta"* ]]; then + echo "AUR_PKG=gitmastery-beta-bin" >> $GITHUB_ENV + echo "INSTALL_NAME=gitmastery-beta" >> $GITHUB_ENV + else + echo "AUR_PKG=gitmastery-bin" >> $GITHUB_ENV + echo "INSTALL_NAME=gitmastery" >> $GITHUB_ENV + fi + - name: Create AUR package repository - run: git clone ssh://aur@aur.archlinux.org/gitmastery-bin.git aur-pkg + run: git clone ssh://aur@aur.archlinux.org/${AUR_PKG}.git aur-pkg - name: Publish to AUR env: - RELEASE_AMD64_URL: https://github.com/git-mastery/app/releases/download/${{ needs.prepare.outputs.ref_name }}/gitmastery-${{ env.VERSION }}-arch-amd64 REF_NAME: ${{ needs.prepare.outputs.ref_name }} run: | cd aur-pkg - BINARY_NAME=gitmastery-${VERSION}-linux-${ARCHITECTURE} + BINARY_NAME=${AUR_PKG}-${VERSION}-linux-${ARCHITECTURE} + RELEASE_AMD64_URL="https://github.com/git-mastery/app/releases/download/${REF_NAME}/${BINARY_NAME}" echo -e $"""$CHANGELOG_MESSAGE \n""" >> gitmastery.changelog cat gitmastery.changelog echo """# Maintainer: Jiahao, Woo - pkgname=gitmastery-bin + pkgname=${AUR_PKG} pkgver=\"$REF_NAME\" pkgrel=1 pkgdesc=\"Git-Mastery CLI for practicing Git\" @@ -383,13 +427,19 @@ jobs: run: | $version_content = '__version__ = "{0}"' -f $env:REF_NAME $version_content | Out-File -FilePath app/version.py -Encoding utf8 - uv run pyinstaller --onefile --name gitmastery main.py + if ($env:REF_NAME -like "*-beta*") { + $name = "gitmastery-beta" + } else { + $name = "gitmastery" + } + uv run pyinstaller --onefile --name $name main.py - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/gitmastery.exe + files: ${{ contains(needs.prepare.outputs.ref_name, 'beta') && 'dist/gitmastery-beta.exe' || 'dist/gitmastery.exe' }} tag_name: ${{ needs.prepare.outputs.ref_name }} + prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -406,7 +456,7 @@ jobs: - name: Submit to WinGet uses: vedantmgoyal9/winget-releaser@v2 with: - identifier: GitMastery.GitMastery + identifier: ${{ contains(needs.prepare.outputs.ref_name, 'beta') && 'GitMastery.GitMasteryBeta' || 'GitMastery.GitMastery' }} version: ${{ needs.prepare.outputs.version_number }} release-tag: ${{ needs.prepare.outputs.ref_name }} token: ${{ secrets.ORG_PAT }} @@ -450,13 +500,19 @@ jobs: - name: Build binary run: | echo "__version__ = \"$REF_NAME\"" > app/version.py - uv run pyinstaller --onefile --name "gitmastery-$ARCHITECTURE" main.py + if [[ "$REF_NAME" == *"-beta"* ]]; then + BINARY_PREFIX="gitmastery-beta" + else + BINARY_PREFIX="gitmastery" + fi + uv run pyinstaller --onefile --name "$BINARY_PREFIX-$ARCHITECTURE" main.py - name: Generate SHA256 (amd64) if: matrix.arch == 'amd64' id: checksum-amd64 run: | - FILENAME=gitmastery-amd64 + [[ "$REF_NAME" == *"-beta"* ]] && PREFIX="gitmastery-beta" || PREFIX="gitmastery" + FILENAME=$PREFIX-amd64 SHA256=$(shasum -a 256 dist/$FILENAME | cut -d ' ' -f1) echo "sha256=$SHA256" >> $GITHUB_OUTPUT @@ -464,15 +520,17 @@ jobs: if: matrix.arch == 'arm64' id: checksum-arm64 run: | - FILENAME=gitmastery-arm64 + [[ "$REF_NAME" == *"-beta"* ]] && PREFIX="gitmastery-beta" || PREFIX="gitmastery" + FILENAME=$PREFIX-arm64 SHA256=$(shasum -a 256 dist/$FILENAME | cut -d ' ' -f1) echo "sha256=$SHA256" >> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/gitmastery-${{ matrix.arch }} + files: dist/${{ steps.build.outputs.binary_prefix }}-${{ matrix.arch }} tag_name: ${{ needs.prepare.outputs.ref_name }} + prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -495,41 +553,53 @@ jobs: git clone https://x-access-token:${GH_TOKEN}@github.com/git-mastery/homebrew-gitmastery.git cd homebrew-gitmastery - cat < gitmastery.rb - class Gitmastery < Formula + if [[ "$REF_NAME" == *"-beta"* ]]; then + FORMULA="gitmastery-beta.rb" + CLASS="GitmasteryBeta" + BINARY_PREFIX="gitmastery-beta" + INSTALL_NAME="gitmastery-beta" + else + FORMULA="gitmastery.rb" + CLASS="Gitmastery" + BINARY_PREFIX="gitmastery" + INSTALL_NAME="gitmastery" + fi + + cat < $FORMULA + class $CLASS < Formula desc "CLI tool for Git-Mastery" homepage "https://github.com/git-mastery/cli" version "$VERSION_NUMBER" on_arm do - url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/gitmastery-arm64" + url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/${BINARY_PREFIX}-arm64" sha256 "${{ needs.macos-build.outputs.sha256-arm64 }}" end on_intel do - url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/gitmastery-amd64" + url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/${BINARY_PREFIX}-amd64" sha256 "${{ needs.macos-build.outputs.sha256-amd64 }}" end def install if Hardware::CPU.arm? - chmod 0755, "gitmastery-arm64" - bin.install "gitmastery-arm64" => "gitmastery" + chmod 0755, "${BINARY_PREFIX}-arm64" + bin.install "${BINARY_PREFIX}-arm64" => "${INSTALL_NAME}" else - chmod 0755, "gitmastery-amd64" - bin.install "gitmastery-amd64" => "gitmastery" + chmod 0755, "${BINARY_PREFIX}-amd64" + bin.install "${BINARY_PREFIX}-amd64" => "${INSTALL_NAME}" end end test do - system "#{bin}/gitmastery", "--help" + system "#{bin}/${INSTALL_NAME}", "--help" end end EOF git remote set-url origin https://x-access-token:${GH_TOKEN}@github.com/git-mastery/homebrew-gitmastery.git git remote -v - git add gitmastery.rb + git add $FORMULA git commit -m "Update to ${REF_NAME}" git push origin main @@ -540,11 +610,17 @@ jobs: runs-on: ${{ matrix.os }} - needs: macos-publish + needs: [prepare, macos-publish] steps: - run: | brew tap git-mastery/gitmastery - brew install gitmastery - file "$(brew --prefix)/bin/gitmastery" - gitmastery --help + if [[ "${{ needs.prepare.outputs.ref_name }}" == *"-beta"* ]]; then + brew install gitmastery-beta + file "$(brew --prefix)/bin/gitmastery-beta" + gitmastery-beta --help + else + brew install gitmastery + file "$(brew --prefix)/bin/gitmastery" + gitmastery --help + fi From 3ca4e3ccb9310fbeddecfef16989726c72a5249d Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 1 Apr 2026 14:27:06 +0800 Subject: [PATCH 02/11] Add semver handling --- .github/workflows/publish.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d948be5..86b6227 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -128,6 +128,8 @@ jobs: else echo "prefix=gitmastery" >> $GITHUB_OUTPUT fi + DEBIAN_VERSION=$(echo "${VERSION}" | sed 's/-beta\./~beta/g') + echo "debian_version=$DEBIAN_VERSION" >> $GITHUB_OUTPUT - name: Create folder structure for ${{ env.ARCHITECTURE }} distribution run: | @@ -142,7 +144,7 @@ jobs: - name: Create upstream tarball .orig.tar.gz run: | # Create .orig.tar.gz file - tar -czf ${{ steps.pkg.outputs.prefix }}_${VERSION}.orig.tar.gz ${{ steps.pkg.outputs.prefix }}-${VERSION}-${ARCHITECTURE}/${{ steps.pkg.outputs.prefix }}-${VERSION}-linux-${ARCHITECTURE} + tar -czf ${{ steps.pkg.outputs.prefix }}_${{ steps.pkg.outputs.debian_version }}.orig.tar.gz ${{ steps.pkg.outputs.prefix }}-${VERSION}-${ARCHITECTURE}/${{ steps.pkg.outputs.prefix }}-${VERSION}-linux-${ARCHITECTURE} - name: Generate Debian packaging files working-directory: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-${{ env.ARCHITECTURE }} @@ -158,7 +160,10 @@ jobs: # Generate the changelog # TODO: Maybe detect if major version change, then make it urgent - dch --create -v ${VERSION}-1 -u low --package ${PKG} "$CHANGELOG_MESSAGE" + + # Changing -beta. to ~beta for Debian semver handling + DEBIAN_VERSION=$(echo "${VERSION}" | sed 's/-beta\./~beta/g') + dch --create -v ${DEBIAN_VERSION}-1 -u low --package ${PKG} "$CHANGELOG_MESSAGE" # Create the control file # TODO: Maybe detect if major version change, then make it mandatory @@ -209,7 +214,7 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: ${{ steps.pkg.outputs.prefix }}_${{ env.VERSION }}-1_${{ env.ARCHITECTURE }}.deb + files: ${{ steps.pkg.outputs.prefix }}_${{ steps.pkg.outputs.debian_version }}-1_${{ env.ARCHITECTURE }}.deb tag_name: ${{ needs.prepare.outputs.ref_name }} prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: @@ -355,6 +360,7 @@ jobs: BINARY_NAME=${AUR_PKG}-${VERSION}-linux-${ARCHITECTURE} RELEASE_AMD64_URL="https://github.com/git-mastery/app/releases/download/${REF_NAME}/${BINARY_NAME}" + PKGVER=$(echo "$REF_NAME" | sed 's/-/_/g') # pkgver cannot have a hyphen (handles beta tags) echo -e $"""$CHANGELOG_MESSAGE \n""" >> gitmastery.changelog @@ -362,7 +368,7 @@ jobs: echo """# Maintainer: Jiahao, Woo pkgname=${AUR_PKG} - pkgver=\"$REF_NAME\" + pkgver=\"$PKGVER\" pkgrel=1 pkgdesc=\"Git-Mastery CLI for practicing Git\" arch=('x86_64') From 73438216973bb1e1b54ceaf9c0356e5a53e3a56c Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 8 Apr 2026 13:33:36 +0800 Subject: [PATCH 03/11] Prevent publishing to winget for beta versions (temporary) --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 86b6227..f1c7685 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -451,7 +451,7 @@ jobs: winget-publish: needs: [prepare, windows] - if: needs.prepare.outputs.should_publish == 'true' + if: needs.prepare.outputs.should_publish == 'true' && !contains(needs.prepare.outputs.ref_name, 'beta') runs-on: windows-latest @@ -462,7 +462,7 @@ jobs: - name: Submit to WinGet uses: vedantmgoyal9/winget-releaser@v2 with: - identifier: ${{ contains(needs.prepare.outputs.ref_name, 'beta') && 'GitMastery.GitMasteryBeta' || 'GitMastery.GitMastery' }} + identifier: GitMastery.GitMastery version: ${{ needs.prepare.outputs.version_number }} release-tag: ${{ needs.prepare.outputs.ref_name }} token: ${{ secrets.ORG_PAT }} From 734eb9dfd52a6d865f1d379f5fa638b17b03d66c Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Fri, 10 Apr 2026 15:53:07 +0800 Subject: [PATCH 04/11] Add handling for beta iterations to Version class --- app/utils/git.py | 2 +- app/utils/version.py | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/utils/git.py b/app/utils/git.py index 9c72035..0f1c6f5 100644 --- a/app/utils/git.py +++ b/app/utils/git.py @@ -4,7 +4,7 @@ from app.utils.command import run from app.utils.version import Version -MIN_GIT_VERSION = Version(2, 28, 0) +MIN_GIT_VERSION = Version(2, 28, 0, None) def init() -> None: diff --git a/app/utils/version.py b/app/utils/version.py index 3ede378..d025840 100644 --- a/app/utils/version.py +++ b/app/utils/version.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Optional @dataclass @@ -6,33 +7,45 @@ class Version: major: int minor: int patch: int + prerelease: Optional[int] @staticmethod def parse_version_string(version: str) -> "Version": """Parse a version string with 'v' prefix (e.g., 'v1.2.3').""" only_version = version[1:] + if "beta" in only_version: + [major, minor, patch, prerelease] = [part[0] for part in only_version.split(".")] + return Version(int(major), int(minor), int(patch), int(prerelease)) [major, minor, patch] = only_version.split(".") - return Version(int(major), int(minor), int(patch)) + return Version(int(major), int(minor), int(patch), None) @staticmethod def parse(version: str) -> "Version": """Parse a plain version string (e.g., '1.2.3').""" parts = version.split(".") - if len(parts) != 3: + if ("beta" in version and len(parts) != 4) or len(parts) != 3: raise ValueError( - f"Invalid version string (expected 'MAJOR.MINOR.PATCH'): {version!r}" + f"Invalid version string (expected 'MAJOR.MINOR.PATCH[-beta.PRERELEASE]'): {version!r}" ) try: - major, minor, patch = (int(part) for part in parts) + if len(parts) == 4: + major, minor, patch, prerelease = (int(part[0]) for part in parts) + else: + major, minor, patch = (int(part) for part in parts) + prerelease = None except ValueError as exc: raise ValueError( f"Invalid numeric components in version string: {version!r}" ) from exc - return Version(major, minor, patch) + return Version(major, minor, patch, prerelease) def is_behind(self, other: "Version") -> bool: """Returns if the current version is behind the other version based on major and minor versions.""" + if self.prerelease and other.prerelease: + return (other.major, other.minor, other.prerelease) > (self.major, self.minor, self.prerelease) return (other.major, other.minor) > (self.major, self.minor) def __repr__(self) -> str: + if self.prerelease: + return f"v{self.major}.{self.minor}.{self.patch}-beta.{self.prerelease}" return f"v{self.major}.{self.minor}.{self.patch}" From ca1026d5fcfa0e5628c760b8a15b4312ba74c102 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Mon, 13 Apr 2026 13:41:26 +0800 Subject: [PATCH 05/11] Refactor to work on manual trigger and do housekeeping by using separate file --- .github/workflows/bump-version-beta.yml | 13 + .github/workflows/bump-version.yml | 2 +- .github/workflows/publish-beta.yml | 565 ++++++++++++++++++++++++ .github/workflows/publish.yml | 182 +++----- 4 files changed, 629 insertions(+), 133 deletions(-) create mode 100644 .github/workflows/bump-version-beta.yml create mode 100644 .github/workflows/publish-beta.yml diff --git a/.github/workflows/bump-version-beta.yml b/.github/workflows/bump-version-beta.yml new file mode 100644 index 0000000..1f75e3c --- /dev/null +++ b/.github/workflows/bump-version-beta.yml @@ -0,0 +1,13 @@ +name: Bump beta version tag + +on: + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + tag: + uses: git-mastery/actions/.github/workflows/bump-version-beta.yml@main + secrets: inherit diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index bf9332b..f4b9a88 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -3,7 +3,7 @@ name: Bump version tag on merge on: pull_request_target: types: [closed] - branches: [main, development] + branches: [main] permissions: contents: write diff --git a/.github/workflows/publish-beta.yml b/.github/workflows/publish-beta.yml new file mode 100644 index 0000000..b2be27b --- /dev/null +++ b/.github/workflows/publish-beta.yml @@ -0,0 +1,565 @@ +name: Build and release Git-Mastery beta CLI + +on: + workflow_run: + workflows: + - Bump beta version tag + types: + - completed + workflow_dispatch: + push: + tags: + - "v*.*.*-beta.*" + +permissions: + contents: write + pull-requests: write + packages: read + issues: read + +jobs: + prepare: + uses: git-mastery/actions/.github/workflows/get-latest-tag.yml@main + secrets: inherit + + linux-build: + needs: prepare + if: needs.prepare.outputs.should_publish == 'true' + + strategy: + matrix: + include: + - os: ubuntu-latest + arch: amd64 + - os: ubuntu-24.04-arm + arch: arm64 + + runs-on: ${{ matrix.os }} + + env: + ARCHITECTURE: ${{ matrix.arch }} + VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} + FILENAME: gitmastery-beta-${{ needs.prepare.outputs.version_number }}-linux-${{ matrix.arch }} + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + + steps: + - name: Checkout source + uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Build binary + run: | + echo "__version__ = \"$REF_NAME\"" > app/version.py + uv run pyinstaller --onefile main.py --name $FILENAME + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/${{ env.FILENAME }} + tag_name: ${{ env.REF_NAME }} + prerelease: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish package as artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ env.FILENAME }} + path: dist/${{ env.FILENAME }} + + debian-build: + # We support both ARM64 and AMD64 since Debian comes with support for + # these two out of the box + needs: [prepare, linux-build] + if: needs.prepare.outputs.should_publish == 'true' + + strategy: + matrix: + include: + - os: ubuntu-latest + arch: amd64 + - os: ubuntu-24.04-arm + arch: arm64 + + runs-on: ${{ matrix.os }} + + env: + ARCHITECTURE: ${{ matrix.arch }} + VERSION: ${{ needs.prepare.outputs.version_number }} + + steps: + - name: Checkout source + uses: actions/checkout@v6 + with: + path: "app" + fetch-depth: 0 + + - name: Extract variables + env: + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + run: | + # Get the tag's commit message + cd app/ + CHANGELOG_MESSAGE=$(git show ${REF_NAME} --no-patch --pretty=format:%s) + echo "CHANGELOG_MESSAGE=${CHANGELOG_MESSAGE}" >> $GITHUB_ENV + + - name: Install Debian packaging tools + run: | + sudo apt-get install devscripts build-essential debhelper-compat + + - name: Set package version + id: pkg + run: | + DEBIAN_VERSION=$(echo "${VERSION}" | sed 's/-beta\./~beta/g') + echo "debian_version=$DEBIAN_VERSION" >> $GITHUB_OUTPUT + + - name: Create folder structure for ${{ env.ARCHITECTURE }} distribution + run: | + mkdir gitmastery-beta-${VERSION}-${ARCHITECTURE} + + - name: Download ${{ env.ARCHITECTURE }} binaries from artifacts + uses: actions/download-artifact@v8 + with: + name: gitmastery-beta-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }} + path: gitmastery-beta-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/ + + - name: Create upstream tarball .orig.tar.gz + run: | + # Create .orig.tar.gz file + tar -czf gitmastery-beta_${{ steps.pkg.outputs.debian_version }}.orig.tar.gz gitmastery-beta-${VERSION}-${ARCHITECTURE}/gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} + + - name: Generate Debian packaging files + working-directory: gitmastery-beta-${{ env.VERSION }}-${{ env.ARCHITECTURE }} + # TODO: Update to something agnostic + env: + EMAIL: woojiahao1234@gmail.com + NAME: Jiahao, Woo + run: | + file gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} + # Create the debian folder + mkdir debian + + # Generate the changelog + # TODO: Maybe detect if major version change, then make it urgent + + # Changing -beta. to ~beta for Debian semver handling + DEBIAN_VERSION=$(echo "${VERSION}" | sed 's/-beta\./~beta/g') + dch --create -v ${DEBIAN_VERSION}-1 -u low --package gitmastery-beta "$CHANGELOG_MESSAGE" + + # Create the control file + # TODO: Maybe detect if major version change, then make it mandatory + echo """Source: gitmastery-beta + Maintainer: $NAME <$EMAIL> + Section: misc + Priority: optional + Standards-Version: 4.7.0 + Build-Depends: debhelper-compat (= 13) + + Package: gitmastery-beta + Architecture: ${ARCHITECTURE} + Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 (>= 2.35), python3 + Description: execute Git-Mastery + gitmastery-beta is a Git learning tool built by the National University of Singapore School of Computing + """ > debian/control + + # Copy over the MIT license from the main app to this release + cat ../app/LICENSE > debian/copyright + + mkdir debian/source + echo "3.0 (quilt)" > debian/source/format + + # Provide the rules for installation, using -e to preserve the tab character as per: + # https://wiki.debian.org/Packaging/Intro + # $(DESTDIR) resolves to debian/binarypackage/ as seen in + # https://www.debian.org/doc/manuals/debmake-doc/ch06.en.html#ftn.idp1797 + echo -e $"""#!/usr/bin/make -f + %: + \tdh \$@ + \n + override_dh_auto_install: + \tinstall -D -m 0755 gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} debian/gitmastery-beta/usr/bin/gitmastery-beta + """ > debian/rules + + echo """usr/bin + """ > debian/gitmastery-beta.dirs + + mkdir -p debian/source + echo """gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} + """ > debian/source/include-binaries + + cat debian/rules + + # Build the package + dpkg-buildpackage -us -uc -a ${ARCHITECTURE} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: gitmastery-beta_${{ steps.pkg.outputs.debian_version }}-1_${{ env.ARCHITECTURE }}.deb + tag_name: ${{ needs.prepare.outputs.ref_name }} + prerelease: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + debian-publish-apt: + needs: [prepare, debian-build] + if: needs.prepare.outputs.should_publish == 'true' + + permissions: write-all + uses: git-mastery/gitmastery-beta-apt-repo/.github/workflows/debian-apt-repo-beta.yml@main + with: + version: ${{ needs.prepare.outputs.ref_name }} + secrets: inherit + + arch-build: + needs: prepare + if: needs.prepare.outputs.should_publish == 'true' + + runs-on: ubuntu-latest + + env: + ARCHITECTURE: amd64 + VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} + FILENAME: gitmastery-beta-${{ needs.prepare.outputs.version_number }}-arch-amd64 + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + + steps: + - name: Checkout source + uses: actions/checkout@v6 + + - name: Build binary + run: | + echo "__version__ = \"$REF_NAME\"" > app/version.py + + docker run --rm \ + -v $PWD:/pkg \ + archlinux:base-devel \ + bash -c " + pacman -Sy --noconfirm curl openssl git && + curl -LsSf https://astral.sh/uv/install.sh | sh && + export PATH=\"/root/.local/bin:\$PATH\" && + cd /pkg && + uv sync && + uv run pyinstaller --onefile main.py --name $FILENAME --distpath /pkg/dist + " + + # Fix file ownership after Docker run + sudo chown -R $(id -u):$(id -g) . + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/${{ env.FILENAME }} + tag_name: ${{ env.REF_NAME }} + prerelease: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish package as artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ env.FILENAME }} + path: dist/${{ env.FILENAME }} + + arch-publish: + # Since Arch linux currently only supports x86_64 out of the box, we will focus + # on supporting that first + needs: [prepare, arch-build] + if: needs.prepare.outputs.should_publish == 'true' + + runs-on: ubuntu-latest + + env: + ARCHITECTURE: amd64 + VERSION: ${{ needs.prepare.outputs.version_number }} + + environment: Main + + steps: + - name: Checkout source + uses: actions/checkout@v6 + with: + path: "app" + fetch-depth: 0 + + - name: Set environment variables + env: + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + run: | + # Get the tag's commit message + cd app/ + CHANGELOG_MESSAGE=$(git show ${REF_NAME} --no-patch --pretty=format:%s) + echo "CHANGELOG_MESSAGE=${CHANGELOG_MESSAGE}" >> $GITHUB_ENV + + - name: Setup SSH for Github Actions + env: + AUR_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + run: | + mkdir -p ~/.ssh + echo "${AUR_PRIVATE_KEY}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + # TODO: Maybe swap to a SoC specific account + git config --global user.name "Jiahao, Woo" + git config --global user.email "woojiahao1234@gmail.com" + git config --global init.defaultBranch master + + - name: Create AUR package repository + run: git clone ssh://aur@aur.archlinux.org/gitmastery-beta-bin.git aur-pkg + + - name: Publish to AUR + env: + RELEASE_AMD64_URL: https://github.com/git-mastery/app/releases/download/${{ needs.prepare.outputs.ref_name }}/gitmastery-beta-${{ env.VERSION }}-arch-amd64 + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + run: | + cd aur-pkg + + BINARY_NAME=gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} + PKGVER=$(echo "$REF_NAME" | sed 's/-/_/g') # pkgver cannot have a hyphen (handles beta tags) + + echo -e $"""$CHANGELOG_MESSAGE + \n""" >> gitmastery.changelog + cat gitmastery.changelog + + echo """# Maintainer: Jiahao, Woo + pkgname=gitmastery-beta-bin + pkgver=\"$PKGVER\" + pkgrel=1 + pkgdesc=\"Git-Mastery CLI for practicing Git\" + arch=('x86_64') + url='https://github.com/git-mastery/app' + license=('MIT') + depends=( + 'python' + ) + changelog=\"gitmastery.changelog\" + source=(\"${BINARY_NAME}::${RELEASE_AMD64_URL}\") + sha256sums=('SKIP') + + package() { + install -D -m 0755 \"\$srcdir/$BINARY_NAME\" \"\$pkgdir/usr/bin/gitmastery\" + chmod 755 \"\$pkgdir/usr/bin/gitmastery\" + } + """ >> PKGBUILD + cat PKGBUILD + + # Generate the .SRCINFO within a Docker container + # We attach the current directory (aur-pkg) as the pkg directory volume + docker run --rm \ + -v $PWD:/pkg \ + archlinux:base-devel \ + bash -c " + pacman -Sy --noconfirm sudo git base-devel && \ + useradd -m builder && \ + echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && \ + chown -R builder:builder /pkg && \ + su builder -c 'cd /pkg && makepkg --printsrcinfo > .SRCINFO' + " + # Fix file ownership after Docker run + sudo chown -R $(id -u):$(id -g) . + + git add . + git commit -m "Update package" + git push --force + + windows: + needs: prepare + if: needs.prepare.outputs.should_publish == 'true' + + runs-on: windows-latest + + steps: + - name: Checkout source + uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Build binary + shell: pwsh + env: + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + run: | + $version_content = '__version__ = "{0}"' -f $env:REF_NAME + $version_content | Out-File -FilePath app/version.py -Encoding utf8 + uv run pyinstaller --onefile --name gitmastery-beta main.py + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/gitmastery-beta.exe + tag_name: ${{ needs.prepare.outputs.ref_name }} + prerelease: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + winget-publish: + needs: [prepare, windows] + if: needs.prepare.outputs.should_publish == 'true' && !contains(needs.prepare.outputs.ref_name, 'beta') # temporarily disable winget publishing + + runs-on: windows-latest + + permissions: + contents: read + + steps: + - name: Submit to WinGet + uses: vedantmgoyal9/winget-releaser@v2 + with: + identifier: GitMastery.GitMasteryBeta + version: ${{ needs.prepare.outputs.version_number }} + release-tag: ${{ needs.prepare.outputs.ref_name }} + token: ${{ secrets.ORG_PAT }} + installers-regex: '\.exe$' + + macos-build: + needs: prepare + if: needs.prepare.outputs.should_publish == 'true' + + # We use macos-15-intel since it's the latest image that currently supports AMD64 + strategy: + matrix: + include: + - os: macos-15-intel + arch: amd64 + - os: macos-latest + arch: arm64 + runs-on: ${{ matrix.os }} + + outputs: + sha256-arm64: ${{ steps.checksum-arm64.outputs.sha256 }} + sha256-amd64: ${{ steps.checksum-amd64.outputs.sha256 }} + + env: + ARCHITECTURE: ${{ matrix.arch }} + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + + steps: + - name: Checkout source + uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Build binary + run: | + echo "__version__ = \"$REF_NAME\"" > app/version.py + uv run pyinstaller --onefile --name "gitmastery-beta-$ARCHITECTURE" main.py + + - name: Generate SHA256 (amd64) + if: matrix.arch == 'amd64' + id: checksum-amd64 + run: | + FILENAME=gitmastery-beta-amd64 + SHA256=$(shasum -a 256 dist/$FILENAME | cut -d ' ' -f1) + echo "sha256=$SHA256" >> $GITHUB_OUTPUT + + - name: Generate SHA256 (arm64) + if: matrix.arch == 'arm64' + id: checksum-arm64 + run: | + FILENAME=gitmastery-beta-arm64 + SHA256=$(shasum -a 256 dist/$FILENAME | cut -d ' ' -f1) + echo "sha256=$SHA256" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/gitmastery-beta-${{ matrix.arch }} + tag_name: ${{ needs.prepare.outputs.ref_name }} + prerelease: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + macos-publish: + needs: [prepare, macos-build] + if: needs.prepare.outputs.should_publish == 'true' + + runs-on: ubuntu-latest + + steps: + - name: Update Homebrew Tap + env: + GH_TOKEN: ${{ secrets.ORG_PAT }} + REF_NAME: ${{ needs.prepare.outputs.ref_name }} + VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + git clone https://x-access-token:${GH_TOKEN}@github.com/git-mastery/homebrew-gitmastery.git + cd homebrew-gitmastery + + cat < gitmastery-beta.rb + class GitmasteryBeta < Formula + desc "CLI tool for Git-Mastery" + homepage "https://github.com/git-mastery/cli" + version "$VERSION_NUMBER" + + on_arm do + url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/gitmastery-beta-arm64" + sha256 "${{ needs.macos-build.outputs.sha256-arm64 }}" + end + + on_intel do + url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/gitmastery-beta-amd64" + sha256 "${{ needs.macos-build.outputs.sha256-amd64 }}" + end + + def install + if Hardware::CPU.arm? + chmod 0755, "gitmastery-beta-arm64" + bin.install "gitmastery-beta-arm64" => "gitmastery-beta" + else + chmod 0755, "gitmastery-beta-amd64" + bin.install "gitmastery-beta-amd64" => "gitmastery-beta" + end + end + + test do + system "#{bin}/gitmastery-beta", "--help" + end + end + EOF + + git remote set-url origin https://x-access-token:${GH_TOKEN}@github.com/git-mastery/homebrew-gitmastery.git + git remote -v + git add gitmastery-beta.rb + git commit -m "Update to ${REF_NAME}" + git push origin main + + macos-test: + strategy: + matrix: + os: [macos-15-intel, macos-latest] + + runs-on: ${{ matrix.os }} + + needs: macos-publish + + steps: + - run: | + brew tap git-mastery/gitmastery + brew install gitmastery-beta + file "$(brew --prefix)/bin/gitmastery-beta" + gitmastery-beta --help \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f1c7685..3197849 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -39,6 +39,7 @@ jobs: env: ARCHITECTURE: ${{ matrix.arch }} VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} + FILENAME: gitmastery-${{ needs.prepare.outputs.version_number }}-linux-${{ matrix.arch }} REF_NAME: ${{ needs.prepare.outputs.ref_name }} steps: @@ -57,28 +58,21 @@ jobs: - name: Build binary run: | echo "__version__ = \"$REF_NAME\"" > app/version.py - if [[ "$REF_NAME" == *"-beta"* ]]; then - FILENAME="gitmastery-beta-${VERSION_NUMBER}-linux-${ARCHITECTURE}" - else - FILENAME="gitmastery-${VERSION_NUMBER}-linux-${ARCHITECTURE}" - fi - echo "filename=$FILENAME" >> $GITHUB_OUTPUT uv run pyinstaller --onefile main.py --name $FILENAME - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/${{ steps.build.outputs.filename }} + files: dist/${{ env.FILENAME }} tag_name: ${{ env.REF_NAME }} - prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish package as artifact uses: actions/upload-artifact@v7 with: - name: ${{ steps.build.outputs.filename }} - path: dist/${{ steps.build.outputs.filename }} + name: ${{ env.FILENAME }} + path: dist/${{ env.FILENAME }} debian-build: # We support both ARM64 and AMD64 since Debian comes with support for @@ -120,65 +114,50 @@ jobs: run: | sudo apt-get install devscripts build-essential debhelper-compat - - name: Set package name - id: pkg - run: | - if [[ "${{ needs.prepare.outputs.ref_name }}" == *"-beta"* ]]; then - echo "prefix=gitmastery-beta" >> $GITHUB_OUTPUT - else - echo "prefix=gitmastery" >> $GITHUB_OUTPUT - fi - DEBIAN_VERSION=$(echo "${VERSION}" | sed 's/-beta\./~beta/g') - echo "debian_version=$DEBIAN_VERSION" >> $GITHUB_OUTPUT - - name: Create folder structure for ${{ env.ARCHITECTURE }} distribution run: | - mkdir ${{ steps.pkg.outputs.prefix }}-${VERSION}-${ARCHITECTURE} + mkdir gitmastery-${VERSION}-${ARCHITECTURE} - name: Download ${{ env.ARCHITECTURE }} binaries from artifacts uses: actions/download-artifact@v8 with: - name: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }} - path: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/ + name: gitmastery-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }} + path: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/ - name: Create upstream tarball .orig.tar.gz run: | # Create .orig.tar.gz file - tar -czf ${{ steps.pkg.outputs.prefix }}_${{ steps.pkg.outputs.debian_version }}.orig.tar.gz ${{ steps.pkg.outputs.prefix }}-${VERSION}-${ARCHITECTURE}/${{ steps.pkg.outputs.prefix }}-${VERSION}-linux-${ARCHITECTURE} + tar -czf gitmastery_${VERSION}.orig.tar.gz gitmastery-${VERSION}-${ARCHITECTURE}/gitmastery-${VERSION}-linux-${ARCHITECTURE} - name: Generate Debian packaging files - working-directory: ${{ steps.pkg.outputs.prefix }}-${{ env.VERSION }}-${{ env.ARCHITECTURE }} + working-directory: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }} # TODO: Update to something agnostic env: EMAIL: woojiahao1234@gmail.com NAME: Jiahao, Woo - PKG: ${{ steps.pkg.outputs.prefix }} run: | - file ${PKG}-${VERSION}-linux-${ARCHITECTURE} + file gitmastery-${VERSION}-linux-${ARCHITECTURE} # Create the debian folder mkdir debian # Generate the changelog # TODO: Maybe detect if major version change, then make it urgent - - # Changing -beta. to ~beta for Debian semver handling - DEBIAN_VERSION=$(echo "${VERSION}" | sed 's/-beta\./~beta/g') - dch --create -v ${DEBIAN_VERSION}-1 -u low --package ${PKG} "$CHANGELOG_MESSAGE" + dch --create -v ${VERSION}-1 -u low --package gitmastery "$CHANGELOG_MESSAGE" # Create the control file # TODO: Maybe detect if major version change, then make it mandatory - echo """Source: ${PKG} + echo """Source: gitmastery Maintainer: $NAME <$EMAIL> Section: misc Priority: optional Standards-Version: 4.7.0 Build-Depends: debhelper-compat (= 13) - Package: ${PKG} + Package: gitmastery Architecture: ${ARCHITECTURE} Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 (>= 2.35), python3 Description: execute Git-Mastery - ${PKG} is a Git learning tool built by the National University of Singapore School of Computing + gitmastery is a Git learning tool built by the National University of Singapore School of Computing """ > debian/control # Copy over the MIT license from the main app to this release @@ -196,14 +175,14 @@ jobs: \tdh \$@ \n override_dh_auto_install: - \tinstall -D -m 0755 ${PKG}-${VERSION}-linux-${ARCHITECTURE} debian/${PKG}/usr/bin/${PKG} + \tinstall -D -m 0755 gitmastery-${VERSION}-linux-${ARCHITECTURE} debian/gitmastery/usr/bin/gitmastery """ > debian/rules echo """usr/bin - """ > debian/${PKG}.dirs + """ > debian/gitmastery.dirs mkdir -p debian/source - echo """${PKG}-${VERSION}-linux-${ARCHITECTURE} + echo """gitmastery-${VERSION}-linux-${ARCHITECTURE} """ > debian/source/include-binaries cat debian/rules @@ -214,15 +193,14 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: ${{ steps.pkg.outputs.prefix }}_${{ steps.pkg.outputs.debian_version }}-1_${{ env.ARCHITECTURE }}.deb + files: gitmastery_${{ env.VERSION }}-1_${{ env.ARCHITECTURE }}.deb tag_name: ${{ needs.prepare.outputs.ref_name }} - prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - debian-publish-apt-stable: + debian-publish-apt: needs: [prepare, debian-build] - if: needs.prepare.outputs.should_publish == 'true' && !contains(needs.prepare.outputs.ref_name, 'beta') + if: needs.prepare.outputs.should_publish == 'true' permissions: write-all uses: git-mastery/gitmastery-apt-repo/.github/workflows/debian-apt-repo.yml@main @@ -230,16 +208,6 @@ jobs: version: ${{ needs.prepare.outputs.ref_name }} secrets: inherit - debian-publish-apt-beta: - needs: [prepare, debian-build] - if: needs.prepare.outputs.should_publish == 'true' && contains(needs.prepare.outputs.ref_name, 'beta') - - permissions: write-all - uses: git-mastery/gitmastery-beta-apt-repo/.github/workflows/debian-apt-repo-beta.yml@main - with: - version: ${{ needs.prepare.outputs.ref_name }} - secrets: inherit - arch-build: needs: prepare if: needs.prepare.outputs.should_publish == 'true' @@ -249,6 +217,7 @@ jobs: env: ARCHITECTURE: amd64 VERSION_NUMBER: ${{ needs.prepare.outputs.version_number }} + FILENAME: gitmastery-${{ needs.prepare.outputs.version_number }}-arch-amd64 REF_NAME: ${{ needs.prepare.outputs.ref_name }} steps: @@ -259,13 +228,6 @@ jobs: run: | echo "__version__ = \"$REF_NAME\"" > app/version.py - if [[ "$REF_NAME" == *"-beta"* ]]; then - FILENAME="gitmastery-beta-${VERSION_NUMBER}-arch-amd64" - else - FILENAME="gitmastery-${VERSION_NUMBER}-arch-amd64" - fi - echo "filename=$FILENAME" >> $GITHUB_OUTPUT - docker run --rm \ -v $PWD:/pkg \ archlinux:base-devel \ @@ -284,17 +246,16 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/${{ steps.build.outputs.filename }} + files: dist/${{ env.FILENAME }} tag_name: ${{ env.REF_NAME }} - prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish package as artifact uses: actions/upload-artifact@v7 with: - name: ${{ steps.build.outputs.filename }} - path: dist/${{ steps.build.outputs.filename }} + name: ${{ env.FILENAME }} + path: dist/${{ env.FILENAME }} arch-publish: # Since Arch linux currently only supports x86_64 out of the box, we will focus @@ -339,36 +300,25 @@ jobs: git config --global user.email "woojiahao1234@gmail.com" git config --global init.defaultBranch master - - name: Select AUR package - run: | - if [[ "$REF_NAME" == *"-beta"* ]]; then - echo "AUR_PKG=gitmastery-beta-bin" >> $GITHUB_ENV - echo "INSTALL_NAME=gitmastery-beta" >> $GITHUB_ENV - else - echo "AUR_PKG=gitmastery-bin" >> $GITHUB_ENV - echo "INSTALL_NAME=gitmastery" >> $GITHUB_ENV - fi - - name: Create AUR package repository - run: git clone ssh://aur@aur.archlinux.org/${AUR_PKG}.git aur-pkg + run: git clone ssh://aur@aur.archlinux.org/gitmastery-bin.git aur-pkg - name: Publish to AUR env: + RELEASE_AMD64_URL: https://github.com/git-mastery/app/releases/download/${{ needs.prepare.outputs.ref_name }}/gitmastery-${{ env.VERSION }}-arch-amd64 REF_NAME: ${{ needs.prepare.outputs.ref_name }} run: | cd aur-pkg - BINARY_NAME=${AUR_PKG}-${VERSION}-linux-${ARCHITECTURE} - RELEASE_AMD64_URL="https://github.com/git-mastery/app/releases/download/${REF_NAME}/${BINARY_NAME}" - PKGVER=$(echo "$REF_NAME" | sed 's/-/_/g') # pkgver cannot have a hyphen (handles beta tags) + BINARY_NAME=gitmastery-${VERSION}-linux-${ARCHITECTURE} echo -e $"""$CHANGELOG_MESSAGE \n""" >> gitmastery.changelog cat gitmastery.changelog echo """# Maintainer: Jiahao, Woo - pkgname=${AUR_PKG} - pkgver=\"$PKGVER\" + pkgname=gitmastery-bin + pkgver=\"$REF_NAME\" pkgrel=1 pkgdesc=\"Git-Mastery CLI for practicing Git\" arch=('x86_64') @@ -433,25 +383,19 @@ jobs: run: | $version_content = '__version__ = "{0}"' -f $env:REF_NAME $version_content | Out-File -FilePath app/version.py -Encoding utf8 - if ($env:REF_NAME -like "*-beta*") { - $name = "gitmastery-beta" - } else { - $name = "gitmastery" - } - uv run pyinstaller --onefile --name $name main.py + uv run pyinstaller --onefile --name gitmastery main.py - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: ${{ contains(needs.prepare.outputs.ref_name, 'beta') && 'dist/gitmastery-beta.exe' || 'dist/gitmastery.exe' }} + files: dist/gitmastery.exe tag_name: ${{ needs.prepare.outputs.ref_name }} - prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} winget-publish: needs: [prepare, windows] - if: needs.prepare.outputs.should_publish == 'true' && !contains(needs.prepare.outputs.ref_name, 'beta') + if: needs.prepare.outputs.should_publish == 'true' runs-on: windows-latest @@ -506,19 +450,13 @@ jobs: - name: Build binary run: | echo "__version__ = \"$REF_NAME\"" > app/version.py - if [[ "$REF_NAME" == *"-beta"* ]]; then - BINARY_PREFIX="gitmastery-beta" - else - BINARY_PREFIX="gitmastery" - fi - uv run pyinstaller --onefile --name "$BINARY_PREFIX-$ARCHITECTURE" main.py + uv run pyinstaller --onefile --name "gitmastery-$ARCHITECTURE" main.py - name: Generate SHA256 (amd64) if: matrix.arch == 'amd64' id: checksum-amd64 run: | - [[ "$REF_NAME" == *"-beta"* ]] && PREFIX="gitmastery-beta" || PREFIX="gitmastery" - FILENAME=$PREFIX-amd64 + FILENAME=gitmastery-amd64 SHA256=$(shasum -a 256 dist/$FILENAME | cut -d ' ' -f1) echo "sha256=$SHA256" >> $GITHUB_OUTPUT @@ -526,17 +464,15 @@ jobs: if: matrix.arch == 'arm64' id: checksum-arm64 run: | - [[ "$REF_NAME" == *"-beta"* ]] && PREFIX="gitmastery-beta" || PREFIX="gitmastery" - FILENAME=$PREFIX-arm64 + FILENAME=gitmastery-arm64 SHA256=$(shasum -a 256 dist/$FILENAME | cut -d ' ' -f1) echo "sha256=$SHA256" >> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/${{ steps.build.outputs.binary_prefix }}-${{ matrix.arch }} + files: dist/gitmastery-${{ matrix.arch }} tag_name: ${{ needs.prepare.outputs.ref_name }} - prerelease: ${{ contains(needs.prepare.outputs.ref_name, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -559,53 +495,41 @@ jobs: git clone https://x-access-token:${GH_TOKEN}@github.com/git-mastery/homebrew-gitmastery.git cd homebrew-gitmastery - if [[ "$REF_NAME" == *"-beta"* ]]; then - FORMULA="gitmastery-beta.rb" - CLASS="GitmasteryBeta" - BINARY_PREFIX="gitmastery-beta" - INSTALL_NAME="gitmastery-beta" - else - FORMULA="gitmastery.rb" - CLASS="Gitmastery" - BINARY_PREFIX="gitmastery" - INSTALL_NAME="gitmastery" - fi - - cat < $FORMULA - class $CLASS < Formula + cat < gitmastery.rb + class Gitmastery < Formula desc "CLI tool for Git-Mastery" homepage "https://github.com/git-mastery/cli" version "$VERSION_NUMBER" on_arm do - url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/${BINARY_PREFIX}-arm64" + url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/gitmastery-arm64" sha256 "${{ needs.macos-build.outputs.sha256-arm64 }}" end on_intel do - url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/${BINARY_PREFIX}-amd64" + url "https://github.com/git-mastery/cli/releases/download/${REF_NAME}/gitmastery-amd64" sha256 "${{ needs.macos-build.outputs.sha256-amd64 }}" end def install if Hardware::CPU.arm? - chmod 0755, "${BINARY_PREFIX}-arm64" - bin.install "${BINARY_PREFIX}-arm64" => "${INSTALL_NAME}" + chmod 0755, "gitmastery-arm64" + bin.install "gitmastery-arm64" => "gitmastery" else - chmod 0755, "${BINARY_PREFIX}-amd64" - bin.install "${BINARY_PREFIX}-amd64" => "${INSTALL_NAME}" + chmod 0755, "gitmastery-amd64" + bin.install "gitmastery-amd64" => "gitmastery" end end test do - system "#{bin}/${INSTALL_NAME}", "--help" + system "#{bin}/gitmastery", "--help" end end EOF git remote set-url origin https://x-access-token:${GH_TOKEN}@github.com/git-mastery/homebrew-gitmastery.git git remote -v - git add $FORMULA + git add gitmastery.rb git commit -m "Update to ${REF_NAME}" git push origin main @@ -616,17 +540,11 @@ jobs: runs-on: ${{ matrix.os }} - needs: [prepare, macos-publish] + needs: macos-publish steps: - run: | brew tap git-mastery/gitmastery - if [[ "${{ needs.prepare.outputs.ref_name }}" == *"-beta"* ]]; then - brew install gitmastery-beta - file "$(brew --prefix)/bin/gitmastery-beta" - gitmastery-beta --help - else - brew install gitmastery - file "$(brew --prefix)/bin/gitmastery" - gitmastery --help - fi + brew install gitmastery + file "$(brew --prefix)/bin/gitmastery" + gitmastery --help \ No newline at end of file From 53fc3157b7eb1044d800181a11ed0d1b6d64f3ca Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 15 Apr 2026 13:39:10 +0800 Subject: [PATCH 06/11] Revert unnecessary changes and fix version logic While the version parsing could be trivially solved using regex matching, I opted to build on the current logic for now. Should maybe come back to it in the future. --- .github/workflows/publish.yml | 2 +- app/utils/git.py | 2 +- app/utils/version.py | 26 +++++++++++++++++--------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3197849..02fb3d1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -547,4 +547,4 @@ jobs: brew tap git-mastery/gitmastery brew install gitmastery file "$(brew --prefix)/bin/gitmastery" - gitmastery --help \ No newline at end of file + gitmastery --help diff --git a/app/utils/git.py b/app/utils/git.py index 0f1c6f5..9c72035 100644 --- a/app/utils/git.py +++ b/app/utils/git.py @@ -4,7 +4,7 @@ from app.utils.command import run from app.utils.version import Version -MIN_GIT_VERSION = Version(2, 28, 0, None) +MIN_GIT_VERSION = Version(2, 28, 0) def init() -> None: diff --git a/app/utils/version.py b/app/utils/version.py index d025840..8c24115 100644 --- a/app/utils/version.py +++ b/app/utils/version.py @@ -7,29 +7,32 @@ class Version: major: int minor: int patch: int - prerelease: Optional[int] + prerelease: Optional[int] = None @staticmethod def parse_version_string(version: str) -> "Version": """Parse a version string with 'v' prefix (e.g., 'v1.2.3').""" only_version = version[1:] if "beta" in only_version: - [major, minor, patch, prerelease] = [part[0] for part in only_version.split(".")] + version_part, prerelease = only_version.split("-beta.") + [major, minor, patch] = version_part.split(".") return Version(int(major), int(minor), int(patch), int(prerelease)) [major, minor, patch] = only_version.split(".") - return Version(int(major), int(minor), int(patch), None) + return Version(int(major), int(minor), int(patch)) @staticmethod def parse(version: str) -> "Version": """Parse a plain version string (e.g., '1.2.3').""" parts = version.split(".") - if ("beta" in version and len(parts) != 4) or len(parts) != 3: + if ("beta" in version and len(parts) != 4) or ("beta" not in version and len(parts) != 3): raise ValueError( f"Invalid version string (expected 'MAJOR.MINOR.PATCH[-beta.PRERELEASE]'): {version!r}" ) try: - if len(parts) == 4: - major, minor, patch, prerelease = (int(part[0]) for part in parts) + if "beta" in version: + version_part, prerelease = version.split("-beta.") + prerelease = int(prerelease) + major, minor, patch = (int(part) for part in version_part.split(".")) else: major, minor, patch = (int(part) for part in parts) prerelease = None @@ -41,11 +44,16 @@ def parse(version: str) -> "Version": def is_behind(self, other: "Version") -> bool: """Returns if the current version is behind the other version based on major and minor versions.""" - if self.prerelease and other.prerelease: - return (other.major, other.minor, other.prerelease) > (self.major, self.minor, self.prerelease) + if self.prerelease is not None and other.prerelease is not None: + return (other.major, other.minor, other.patch, other.prerelease) > ( + self.major, + self.minor, + self.patch, + self.prerelease, + ) return (other.major, other.minor) > (self.major, self.minor) def __repr__(self) -> str: - if self.prerelease: + if self.prerelease is not None: return f"v{self.major}.{self.minor}.{self.patch}-beta.{self.prerelease}" return f"v{self.major}.{self.minor}.{self.patch}" From 01c951978abe70bcef2bcd1bb44f898ab29e4fbc Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 15 Apr 2026 13:43:33 +0800 Subject: [PATCH 07/11] Fix mypy error --- app/utils/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils/version.py b/app/utils/version.py index 8c24115..600a7ee 100644 --- a/app/utils/version.py +++ b/app/utils/version.py @@ -30,8 +30,8 @@ def parse(version: str) -> "Version": ) try: if "beta" in version: - version_part, prerelease = version.split("-beta.") - prerelease = int(prerelease) + version_part, prerelease_str = version.split("-beta.") + prerelease = int(prerelease_str) major, minor, patch = (int(part) for part in version_part.split(".")) else: major, minor, patch = (int(part) for part in parts) From e75ec6ffd04408fa718035cf74eb9a17b3bda579 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 15 Apr 2026 13:45:25 +0800 Subject: [PATCH 08/11] Fix AUR installation directory --- .github/workflows/publish-beta.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-beta.yml b/.github/workflows/publish-beta.yml index b2be27b..de5a05c 100644 --- a/.github/workflows/publish-beta.yml +++ b/.github/workflows/publish-beta.yml @@ -345,8 +345,8 @@ jobs: sha256sums=('SKIP') package() { - install -D -m 0755 \"\$srcdir/$BINARY_NAME\" \"\$pkgdir/usr/bin/gitmastery\" - chmod 755 \"\$pkgdir/usr/bin/gitmastery\" + install -D -m 0755 \"\$srcdir/$BINARY_NAME\" \"\$pkgdir/usr/bin/gitmastery-beta\" + chmod 755 \"\$pkgdir/usr/bin/gitmastery-beta\" } """ >> PKGBUILD cat PKGBUILD From f5d8dcc00a36c0607c328426f933bf64b2695e43 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 15 Apr 2026 14:10:33 +0800 Subject: [PATCH 09/11] Fix changelog name --- .github/workflows/publish-beta.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-beta.yml b/.github/workflows/publish-beta.yml index de5a05c..45b0186 100644 --- a/.github/workflows/publish-beta.yml +++ b/.github/workflows/publish-beta.yml @@ -326,8 +326,8 @@ jobs: PKGVER=$(echo "$REF_NAME" | sed 's/-/_/g') # pkgver cannot have a hyphen (handles beta tags) echo -e $"""$CHANGELOG_MESSAGE - \n""" >> gitmastery.changelog - cat gitmastery.changelog + \n""" >> gitmastery-beta.changelog + cat gitmastery-beta.changelog echo """# Maintainer: Jiahao, Woo pkgname=gitmastery-beta-bin @@ -340,7 +340,7 @@ jobs: depends=( 'python' ) - changelog=\"gitmastery.changelog\" + changelog=\"gitmastery-beta.changelog\" source=(\"${BINARY_NAME}::${RELEASE_AMD64_URL}\") sha256sums=('SKIP') From b5ae1bc04e406fc3bcad7d5ad2b4cb5e916416ef Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 15 Apr 2026 14:12:37 +0800 Subject: [PATCH 10/11] Shift bump-version-beta logic to app repo --- .github/workflows/bump-version-beta.yml | 49 +++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bump-version-beta.yml b/.github/workflows/bump-version-beta.yml index 1f75e3c..23460e7 100644 --- a/.github/workflows/bump-version-beta.yml +++ b/.github/workflows/bump-version-beta.yml @@ -2,12 +2,55 @@ name: Bump beta version tag on: workflow_dispatch: + secrets: + ORG_PAT: + required: true permissions: contents: write - pull-requests: write jobs: tag: - uses: git-mastery/actions/.github/workflows/bump-version-beta.yml@main - secrets: inherit + runs-on: ubuntu-latest + + concurrency: + group: tag-development + cancel-in-progress: false + + env: + PAT: ${{ secrets.ORG_PAT }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Compute next beta tag + id: version + run: | + git fetch --tags + STABLE=$(git tag --list "v*" \ + | grep -Ev -- '-beta' \ + | sort -V \ + | tail -n 1) + STABLE=${STABLE:-v0.0.0} + BASE=${STABLE#v} + EXISTING=$(git tag -l "v$BASE-beta*" | sort -V | tail -n 1) + if [[ -z "$EXISTING" ]]; then + NEXT="v$BASE-beta.1" + else + NUM=$(echo "$EXISTING" | sed -E 's/.*beta\.?([0-9]*)/\1/') + NEXT="v$BASE-beta.$((NUM+1))" + fi + echo "next=$NEXT" >> $GITHUB_OUTPUT + - name: Create and push tag + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + TAG=${{ steps.version.outputs.next }} + + git tag "$TAG" + + git push https://x-access-token:${PAT}@github.com/${{ github.repository }}.git refs/tags/$TAG From 2fc8a4666ec6df9a2fca88c7a77b1f1d0ea8c512 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 15 Apr 2026 14:36:15 +0800 Subject: [PATCH 11/11] Remove incorrect secrets syntax --- .github/workflows/bump-version-beta.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/bump-version-beta.yml b/.github/workflows/bump-version-beta.yml index 23460e7..3a43a6f 100644 --- a/.github/workflows/bump-version-beta.yml +++ b/.github/workflows/bump-version-beta.yml @@ -2,9 +2,6 @@ name: Bump beta version tag on: workflow_dispatch: - secrets: - ORG_PAT: - required: true permissions: contents: write