mirror of
https://github.com/ohmyzsh/ohmyzsh.git
synced 2026-08-07 06:08:39 +02:00
feat(semver-tag): add plugin to bump semver git tags
This commit is contained in:
parent
c5ba74cf02
commit
4a8082e028
2 changed files with 149 additions and 0 deletions
67
plugins/semver-tag/README.md
Normal file
67
plugins/semver-tag/README.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# semver-tag plugin
|
||||
|
||||
Bumps a repo's latest `MAJOR.MINOR.PATCH` git tag and creates the next one, with a confirmation prompt before anything is created.
|
||||
|
||||
To use it, add `semver-tag` to the plugins array in your `.zshrc` file:
|
||||
|
||||
```zsh
|
||||
plugins=(... semver-tag)
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `gtM` | Bump the major version and create the tag (e.g. `1.4.2` -> `2.0.0`) |
|
||||
| `gtm` | Bump the minor version (e.g. `1.4.2` -> `1.5.0`) |
|
||||
| `gtp` | Bump the patch version (e.g. `1.4.2` -> `1.4.3`) |
|
||||
| `gtvM` | Same as `gtM`, but on first use in a repo with no tags, bootstraps with a `v` prefix (`v1.0.0`) |
|
||||
| `gtvm` | Same as `gtm`, `v`-prefixed bootstrap |
|
||||
| `gtvp` | Same as `gtp`, `v`-prefixed bootstrap |
|
||||
|
||||
`M` = major, `m` = minor, `p` = patch (case-sensitive).
|
||||
|
||||
Every command shows what it's about to do and asks for confirmation before creating the tag:
|
||||
|
||||
```
|
||||
$ gtM
|
||||
This will create tag 2.0.0, continue? [y/N]
|
||||
```
|
||||
|
||||
## Bootstrapping a new repo
|
||||
|
||||
If a repo has no tags yet, the version bumps become the starting version instead of erroring:
|
||||
|
||||
```
|
||||
$ gtM # -> 1.0.0
|
||||
$ gtm # -> 0.1.0
|
||||
$ gtp # -> 0.0.1
|
||||
```
|
||||
|
||||
`gtvM`/`gtvm`/`gtvp` do the same but prefix the bootstrapped tag with `v` (`v1.0.0` / `v0.1.0` / `v0.0.1`). Once a repo has a real tag, `v` is no longer a flag you choose - it's inferred from whatever the latest tag actually looks like.
|
||||
|
||||
## Tag format mismatch
|
||||
|
||||
The `v`/no-`v` decision is only yours to make when bootstrapping. Once tags exist, the plugin always bumps using the *real* prefix of the latest tag - it never invents a format the repo doesn't already use. But if what you typed doesn't match what's actually there, you get a warning baked into the confirmation prompt instead of a silent surprise:
|
||||
|
||||
```
|
||||
$ gtM
|
||||
Running this will create v1.0.0, but your existing tags are formatted v0.1.1, are you sure? [y/N]
|
||||
```
|
||||
|
||||
## Latest tag detection
|
||||
|
||||
"Latest" means most recently *created* (`git for-each-ref --sort=-creatordate`), not the highest version number and not `git describe`'s "nearest tag reachable from HEAD." This matters if you ever tag out of numeric order - the plugin follows creation time, not the version string.
|
||||
|
||||
One caveat: for a lightweight tag, "creation time" falls back to the *tagged commit's* date (lightweight tags don't carry their own timestamp). If you bump more than once without an intervening commit, two lightweight tags on the same commit can tie and the ordering between them isn't guaranteed. This doesn't come up in normal use, since a real release always has a new commit before the next tag.
|
||||
|
||||
## Annotated, signed, or otherwise customized tags
|
||||
|
||||
By default every tag is lightweight (`git tag <name>`, no message, no tagger metadata). Anything you pass beyond the command name is forwarded straight to `git tag` after the computed tag name, so you get the rest of `git tag`'s own flags for free:
|
||||
|
||||
```
|
||||
$ gtM --annotate -m "Release notes here"
|
||||
$ gtM -s -m "Signed release" # GPG-signed annotated tag
|
||||
```
|
||||
|
||||
Note that `-m` is required for annotated/signed tags when passed this way - `git tag <name> --annotate "some text"` (no `-m`) is not valid git syntax; without `-m`/`-F`, git opens `$EDITOR` for the message instead, same as running `git tag -a` directly, and refuses to create the tag if you save an empty message.
|
||||
82
plugins/semver-tag/semver-tag.plugin.zsh
Normal file
82
plugins/semver-tag/semver-tag.plugin.zsh
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Bump the repo's latest MAJOR.MINOR.PATCH tag and create a new one.
|
||||
# If no tag exists yet, bootstraps: gtM -> 1.0.0, gtm -> 0.1.0, gtp -> 0.0.1
|
||||
# Usage:
|
||||
# gtM|gtm|gtp lightweight tag
|
||||
# gtvM|gtvm|gtvp (bootstrap only) prefix the first tag with "v"
|
||||
# Case-sensitive: M = major, m = minor, p = patch.
|
||||
# Anything else you pass is forwarded straight to `git tag` after the computed
|
||||
# name, e.g. `gtM --annotate -m "message"` or `gtM -s -m "message"` (signed).
|
||||
_semver_tag_bump() {
|
||||
local part="$1"; shift
|
||||
local v_prefix_requested=false
|
||||
while [[ "$1" == "-v" || "$1" == "--v-prefix" ]]; do
|
||||
v_prefix_requested=true
|
||||
shift
|
||||
done
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
echo "Not inside a git repository." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local latest
|
||||
latest=$(git for-each-ref --sort=-creatordate --format='%(refname:short)' refs/tags --count=1)
|
||||
|
||||
local prefix major minor patch
|
||||
local requested_prefix=""
|
||||
$v_prefix_requested && requested_prefix="v"
|
||||
local mismatch=false
|
||||
|
||||
if [[ -z "$latest" ]]; then
|
||||
prefix="$requested_prefix"
|
||||
case "$part" in
|
||||
major) major=1; minor=0; patch=0 ;;
|
||||
minor) major=0; minor=1; patch=0 ;;
|
||||
patch) major=0; minor=0; patch=1 ;;
|
||||
esac
|
||||
else
|
||||
if [[ ! "$latest" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Latest tag '$latest' isn't MAJOR.MINOR.PATCH (optional v prefix) - refusing to guess the next version." >&2
|
||||
return 1
|
||||
fi
|
||||
prefix=""
|
||||
[[ "$latest" == v* ]] && prefix="v"
|
||||
[[ "$prefix" != "$requested_prefix" ]] && mismatch=true
|
||||
local ver="${latest#v}"
|
||||
IFS='.' read -r major minor patch <<< "$ver"
|
||||
case "$part" in
|
||||
major) major=$((major + 1)); minor=0; patch=0 ;;
|
||||
minor) minor=$((minor + 1)); patch=0 ;;
|
||||
patch) patch=$((patch + 1)) ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
local newtag="${prefix}${major}.${minor}.${patch}"
|
||||
|
||||
if $mismatch; then
|
||||
print -n "Running this will create $newtag, but your existing tags are formatted $latest, are you sure? [y/N] "
|
||||
else
|
||||
print -n "This will create tag $newtag, continue? [y/N] "
|
||||
fi
|
||||
read REPLY
|
||||
if [[ "$REPLY" != [Yy]* ]]; then
|
||||
echo "Aborted."
|
||||
return 1
|
||||
fi
|
||||
|
||||
git tag "$newtag" "$@" || return 1
|
||||
|
||||
if [[ -n "$latest" ]]; then
|
||||
echo "Created tag $newtag (previous latest: $latest)"
|
||||
else
|
||||
echo "Created tag $newtag (first tag in repo)"
|
||||
fi
|
||||
}
|
||||
|
||||
gtM() { _semver_tag_bump major "$@" }
|
||||
gtm() { _semver_tag_bump minor "$@" }
|
||||
gtp() { _semver_tag_bump patch "$@" }
|
||||
|
||||
alias gtvM='gtM -v'
|
||||
alias gtvm='gtm -v'
|
||||
alias gtvp='gtp -v'
|
||||
Loading…
Reference in a new issue