Implement beta publishing on development branch#85
Implement beta publishing on development branch#85VikramGoyal23 wants to merge 5 commits intogit-mastery:mainfrom
Conversation
1707c2d to
a7c9451
Compare
a7c9451 to
275576b
Compare
8c7e033 to
bb5aba5
Compare
|
@VikramGoyal23 Can we check on status of this? Hopefully we can get this merged soon before the semester ends. |
70ffcf3 to
03d3902
Compare
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Set up uv | ||
| uses: astral-sh/setup-uv@v7 |
| uv run pyinstaller --onefile main.py --name $FILENAME | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 |
| dpkg-buildpackage -us -uc -a ${ARCHITECTURE} | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 |
| sudo chown -R $(id -u):$(id -g) . | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 |
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Set up uv | ||
| uses: astral-sh/setup-uv@v7 |
| uv run pyinstaller --onefile --name gitmastery-beta main.py | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 |
|
|
||
| steps: | ||
| - name: Submit to WinGet | ||
| uses: vedantmgoyal9/winget-releaser@v2 |
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Set up uv | ||
| uses: astral-sh/setup-uv@v7 |
| echo "sha256=$SHA256" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 |
|
Can be merged immediately after the actions PR, doesn't need to wait for registration on AUR or Winget. |
There was a problem hiding this comment.
Pull request overview
Implements a beta release/publishing pipeline (tagged vX.Y.Z-beta.N) alongside the existing stable publishing flow, and updates version handling to recognize beta prerelease versions.
Changes:
- Extend
Versionto include an optional beta prerelease component and update parsing/formatting logic. - Add GitHub Actions workflows to bump beta tags and publish beta artifacts/packages across platforms.
- Minor formatting adjustment in the existing publish workflow.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
app/utils/version.py |
Adds prerelease support and beta-aware parsing/printing for version strings. |
app/utils/git.py |
Updates MIN_GIT_VERSION construction to match the new Version signature. |
.github/workflows/publish.yml |
Minor formatting/indentation adjustment. |
.github/workflows/publish-beta.yml |
New workflow to build/release beta binaries and publish to Debian APT, AUR, Homebrew (and placeholder Winget). |
.github/workflows/bump-version-beta.yml |
New workflow that triggers the reusable beta tag bump workflow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| major: int | ||
| minor: int | ||
| patch: int | ||
| prerelease: Optional[int] |
There was a problem hiding this comment.
prerelease is now a required dataclass field, which forces all direct Version(...) call sites to pass an explicit None. Consider giving this field a default of None (and/or using int | None) to keep construction backwards-compatible and reduce call-site churn.
| prerelease: Optional[int] | |
| prerelease: Optional[int] = None |
| 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)) |
There was a problem hiding this comment.
Beta parsing here is incorrect: part[0] takes only the first character of each component, so multi-digit versions like v10.2.3-beta.12 will be mis-parsed (major=1, prerelease=1). Also only_version.split('.') yields a patch component like "3-beta", which should be split on -beta. rather than truncated.
| """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: |
There was a problem hiding this comment.
The validation condition is wrong for beta versions: when version contains beta and parts has 4 elements, len(parts) != 3 still makes this branch raise every time. The condition should distinguish between beta and non-beta formats instead of combining them with or len(parts) != 3.
| 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): |
| 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 |
There was a problem hiding this comment.
When parsing 4-part beta versions, int(part[0]) will truncate multi-digit components and doesn’t actually remove the -beta suffix from the patch component. Use a proper split/regex to extract major.minor.patch and the numeric prerelease (or reject beta strings in parse() if that’s not supported).
| if self.prerelease and other.prerelease: | ||
| return (other.major, other.minor, other.prerelease) > (self.major, self.minor, self.prerelease) |
There was a problem hiding this comment.
is_behind uses truthiness checks for prerelease and compares only (major, minor, prerelease) when both are prereleases. This will treat prerelease=0 as absent, and it can mis-order versions if the patch differs between beta versions (e.g., 1.2.4-beta.1 vs 1.2.3-beta.9). Prefer explicit is not None checks and include patch in the comparison when comparing two prerelease 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, | |
| ) |
There was a problem hiding this comment.
prerelease will never be 0 but the part about patch is valid
| return (other.major, other.minor) > (self.major, self.minor) | ||
|
|
||
| def __repr__(self) -> str: | ||
| if self.prerelease: |
There was a problem hiding this comment.
__repr__ uses if self.prerelease: which will omit the -beta.N suffix when prerelease is 0. Use an explicit is not None check for optional prerelease values.
| if self.prerelease: | |
| if self.prerelease is not None: |
There was a problem hiding this comment.
prerelease will never be 0 but this is still good practice
| install -D -m 0755 \"\$srcdir/$BINARY_NAME\" \"\$pkgdir/usr/bin/gitmastery\" | ||
| chmod 755 \"\$pkgdir/usr/bin/gitmastery\" |
There was a problem hiding this comment.
This AUR package installs the binary as /usr/bin/gitmastery, which will conflict with the stable gitmastery package and doesn’t match the gitmastery-beta-bin naming used elsewhere (Debian/Homebrew use gitmastery-beta). Install it as gitmastery-beta to avoid overwriting the stable CLI.
| 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\" |
| 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 |
There was a problem hiding this comment.
permissions: write-all is overly broad for this job and increases blast radius if a workflow token is misused. Prefer least-privilege permissions (e.g., only the scopes required by the called reusable workflow) and keep it consistent with the top-level permissions block where possible.
jiaxinnns
left a comment
There was a problem hiding this comment.
Some comments. All the suggestions from Copilot are worth noting too, especially the parsing bugs in version.py
| run: | | ||
| cd aur-pkg | ||
|
|
||
| BINARY_NAME=gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} |
There was a problem hiding this comment.
BINARY_NAME=gitmastery-beta-${VERSION}-linux-${ARCHITECTURE} uses the pattern linux in the binary name, but the arch-build job produces artifacts named gitmastery-beta-${VERSION}-arch-amd64 (using arch instead of linux). Is this intentional?
| cat <<EOF > gitmastery-beta.rb | ||
| class GitmasteryBeta < Formula | ||
| desc "CLI tool for Git-Mastery" | ||
| homepage "https://github.com/git-mastery/cli" |
There was a problem hiding this comment.
I assume this was the old name for the app repo, should we standardise to use the new URL https://github.com/git-mastery/app (like in arch-publish) for consistency?
There was a problem hiding this comment.
This is a good catch. While the cli link does redirect to our app repository, it is outdated. For consistency, however, I would then have to update the workflow for stable releases as well (publish.yml). As such, I think it is best to create a separate "chore" PR for this later.
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| files: dist/gitmastery-beta.exe |
There was a problem hiding this comment.
For Linux and macOS, your binaries include the version and architecture in the filename (e.g. gitmastery-beta-1.0.0-linux-amd64). However, in the windows job, you compile and upload it simply as gitmastery-beta.exe. Is there a specific reason for this?
There was a problem hiding this comment.
This is to keep it consistent with the stable GitHub releases for Windows.
|
|
||
| git add . | ||
| git commit -m "Update package" | ||
| git push --force |
There was a problem hiding this comment.
This is a destructive action if another maintainer pushes between steps. We can consider a regular push with rebase/merge strategy
| ) | ||
| changelog=\"gitmastery.changelog\" | ||
| source=(\"${BINARY_NAME}::${RELEASE_AMD64_URL}\") | ||
| sha256sums=('SKIP') |
There was a problem hiding this comment.
Could this potentially be a security issue?
|
|
||
| - name: Generate Debian packaging files | ||
| working-directory: gitmastery-beta-${{ env.VERSION }}-${{ env.ARCHITECTURE }} | ||
| # TODO: Update to something agnostic |
There was a problem hiding this comment.
Should be addressed before merging
There was a problem hiding this comment.
Looking at the revision history for publish.yml (where most of this workflow is copied from), this comment appears to be an accidental leftover from when Woo Jiahao used to define the EMAIL and NAME variables inside the run block. He later shifted their definitions to be inside env (Revision for reference). The "agnostic" comment refers to the name and email, not the working-directory. Since Jiahao is the long-time maintainer of the repository, I think it's best to leave his name and email as the maintainer details.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """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: |
There was a problem hiding this comment.
The validation condition is logically incorrect: (... and len(parts) != 4) or len(parts) != 3 will always raise for beta versions because len(parts) is 4, so len(parts) != 3 is true. Rewrite this as two distinct cases (beta => 4 parts, non-beta => 3 parts).
| if ("beta" in version and len(parts) != 4) or len(parts) != 3: | |
| if "beta" in version: | |
| if len(parts) != 4: | |
| raise ValueError( | |
| f"Invalid version string (expected 'MAJOR.MINOR.PATCH[-beta.PRERELEASE]'): {version!r}" | |
| ) | |
| elif len(parts) != 3: |
| """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) |
There was a problem hiding this comment.
is_behind compares prerelease versions using (major, minor, prerelease) only, which can mis-order beta versions when patch differs (e.g., 1.2.4-beta.1 vs 1.2.3-beta.9). Include patch in the comparison (and ideally use is not None checks for the optional prerelease field).
| """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) | |
| """Returns if the current version is behind the other version based on major, minor, patch, and prerelease versions.""" | |
| 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, other.patch) > ( | |
| self.major, | |
| self.minor, | |
| self.patch, | |
| ) |
| return (other.major, other.minor) > (self.major, self.minor) | ||
|
|
||
| def __repr__(self) -> str: | ||
| if self.prerelease: |
There was a problem hiding this comment.
__repr__ uses a truthiness check (if self.prerelease:). Since prerelease is an Optional[int], this will treat 0 the same as None and omit the -beta.N suffix. Prefer an explicit is not None check for optional fields.
| if self.prerelease: | |
| if self.prerelease is not None: |
| brew install gitmastery | ||
| file "$(brew --prefix)/bin/gitmastery" | ||
| gitmastery --help | ||
| gitmastery --help No newline at end of file |
There was a problem hiding this comment.
Can we revert to to keep the diff cleaner? Would also be good to have the newline at the end as convention as well
There was a problem hiding this comment.
My bad, that slipped through while reverting the file.
| from app.utils.version import Version | ||
|
|
||
| MIN_GIT_VERSION = Version(2, 28, 0) | ||
| MIN_GIT_VERSION = Version(2, 28, 0, None) |
There was a problem hiding this comment.
| MIN_GIT_VERSION = Version(2, 28, 0, None) | |
| MIN_GIT_VERSION = Version(2, 28, 0) |
Prerelease shouldn't be a compulsory field, this should be backwards compatible with existing code. Set default = None if not specified
There was a problem hiding this comment.
As mentioned in the comment in the actions repository, the logic for it should live here
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| winget-publish: |
There was a problem hiding this comment.
Is there a plan to publish on winget too?
|
Other than that, I think you can feel free to push to main one these are fixed, then you can debug while publishing to beta as well to test it out. |
03d3902 to
ca1026d
Compare
Should be merged after the actions PR and after
gitmastery-betais registered on AUR and Winget.RFC: https://docs.google.com/document/d/17ZhVua_eH_mWQIDc90ojq6EkiolRXQa5asBBvZhgfAU/edit?tab=t.0