Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 170 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ Fixes some issues of commitizen-go and supports more new features.

## Features

- Multi-template support
- Support more options of `git commit`
- Use [bubbletea](https://github.com/charmbracelet/bubbletea) instead of [survey](https://github.com/AlecAivazis/survey) ([survey](https://github.com/AlecAivazis/survey) is no longer maintained).
- Better unit tests.
- Multi-template support.
- More powerful and flexible template.
- Support more options of `git commit`.
- Use [huh](https://github.com/charmbracelet/huh) instead of [survey](https://github.com/AlecAivazis/survey) ([survey](https://github.com/AlecAivazis/survey) is no longer maintained).

## Getting Started

Expand Down Expand Up @@ -72,6 +72,7 @@ $ git cz
Download the pre-compiled binaries from the [releases page](https://github.com/shipengqi/commitizen/releases) and copy them to the desired location.

Then install this tool to git-core as git-cz:

```
$ commitizen init
```
Expand All @@ -97,79 +98,203 @@ $ make && ./_output/$(GOOS)/$(GOARCH)/bin/commitizen init

## Configuration

You can set configuration file that `.git-czrc` at repository root or home directory. The configuration file that located in repository root have a priority over the one in home directory. The format is the same as the following:
You can set configuration file that `.czrc` at repository root or home directory. The configuration file that located in repository root have a priority over the one in home directory. The format is the same as the following:

```yaml
name: my-default
default: true # (optional) If true, this template will be used as the default template, note that there can only be one default template
items:
- name: type
desc: "Select the type of change that you're committing:"
type: select
group: page1
label: "Select the type of change that you're committing:"
type: list
options:
- name: feat
desc: "A new feature"
- name: fix
desc: "A bug fix"
- name: docs
desc: "Documentation only changes"
- name: test
desc: "Adding missing tests"
- name: WIP
desc: "Work in progress"
- name: chore
desc: "Changes to the build process or auxiliary tools\n and libraries such as documentation generation"
- name: style
desc: "Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)"
- name: refactor
desc: "A code change that neither fixes a bug nor adds a feature"
- name: perf
desc: "A code change that improves performance"
- name: revert
desc: "Revert to a commit"
- value: feat
key: "feat: A new feature"
- value: fix
key: "fix: A bug fix"
- value: docs
key: "docs: Documentation only changes"
- value: test
key: "test: Adding missing or correcting existing tests"
- value: chore
key: "chore: Changes to the build process or auxiliary tools and\n libraries such as documentation generation"
- value: style
key: "style: Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)"
- value: refactor
key: "refactor: A code change that neither fixes a bug nor adds a feature"
- value: perf
key: "perf: A code change that improves performance"
- value: revert
key: "revert: Reverts a previous commit"
- name: scope
desc: "Scope. Could be anything specifying place of the commit change:"
type: input
group: page2
label: "Scope. What is the scope of this change? (class or file name):"
type: string
regex: ^[a-zA-Z0-9]+
- name: subject
desc: "Subject. Concise description of the changes. Imperative, lower case and no final dot:"
type: input
required: true # (optional) If true, enable a validator that requires the control have a non-empty value.
group: page2
label: "Subject. Write a short and imperative summary of the code change (lower case and no period):"
type: string
required: true
- name: body
desc: "Body. Motivation for the change and contrast this with previous behavior:"
type: textarea
group: page3
label: "Body. Provide additional contextual information about the code changes:"
type: text
- name: footer
desc: "Footer. Information about Breaking Changes and reference issues that this commit closes:"
type: textarea
group: page3
label: "Footer. Information about Breaking Changes and reference issues that this commit closes:"
type: text
format: "{{.type}}{{with .scope}}({{.}}){{end}}: {{.subject}}{{with .body}}\n\n{{.}}{{end}}{{with .footer}}\n\n{{.}}{{end}}"`
```

### Format

Commit message `format`:

```
format: "{{.type}}{{with .scope}}({{.}}){{end}}: {{.subject}}{{with .body}}\n\n{{.}}{{end}}{{with .footer}}\n\n{{.}}{{end}}"
```

### Items

#### Common Item Properties

| Property | Required | Default Value | Description |
|:------------|:---------|:--------------|:--------------------------------------------------------------------------------------------------------------------|
| name | yes | - | Unique identifier for the item. |
| label | yes | - | This will be used as the label for the input field in the UI. |
| type | yes | - | The type of item. Determines which UI widget is shown. See the Item Types section to see all the different options. |
| group | no | - | The name of the group this item belongs to. |
| description | no | - | A short description of the item for user guidance. This will be displayed along with the input field. |

#### Item Types

- string
- text
- integer
- boolean
- secret
- list
- multi_list

#### string

`string` are single line text parameters.

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:-------------------------------------------------------------------------------------------------------------|
| required | no | `false` | Whether a string value is required or not. |
| fqdn | no | `false` | Add a preset FQDN regex to validate string. |
| ip | no | `false` | Add a preset IPv4/IPv6 regex to validate string. |
| trim | no | `false` | If true, will remove the leading and trailing blank characters before submit. |
| default_value | no | - | The default value for this item. |
| regex | no | - | A regex used to validate the string. |
| min_length | no | - | The minimum length of the string. If the value is not required and no value has been given, this is ignored. |
| max_length | no | - | The maximum length of the string. |

#### text

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:-----------------------------------------------------------------------------------------------------------|
| required | no | `false` | Whether the text is required or not. |
| height | no | 5 | The height of the text. |
| default_value | no | - | The default value for this item. |
| regex | no | - | A regex used to validate the text. |
| min_length | no | - | The minimum length of the text. If the value is not required and no value has been given, this is ignored. |
| max_length | no | - | The maximum length of the text. |

#### integer

`integer` is a number.

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:----------------------------------------|
| required | no | `false` | Whether the integer is required or not. |
| default_value | no | - | The default value for this item. |
| min | no | - | The minimum value allowed. |
| max | no | - | The maximum value allowed. |

#### boolean

`boolean` are true or false values.

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:---------------------------------|
| default_value | no | - | The default value for this item. |

#### secret

`secret` is used for sensitive data that should not be echoed in the UI, for example, passwords.

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:-------------------------------------------------------------------------------------------------------------|
| required | no | `false` | Whether the secret is required or not. |
| trim | no | `false` | If true, will remove the leading and trailing blank characters before submit. |
| default_value | no | - | The default value for this item. |
| regex | no | - | A regex used to validate the secret. |
| min_length | no | - | The minimum length of the secret. If the value is not required and no value has been given, this is ignored. |
| max_length | no | - | The maximum length of the secret. |

#### list

`list` is predefined lists of values that can be picked by the user.

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:-------------------------------------------|
| required | no | `false` | Whether a string value is required or not. |
| default_value | no | - | The default value for this item. |
| options | yes | - | The list of options to choose from. |

#### multi_list

Similar to `list`, but with multiple selection.

Properties:

| Property | Required | Default Value | Description |
|:--------------|:---------|:--------------|:-------------------------------------------|
| required | no | `false` | Whether a string value is required or not. |
| default_value | no | - | A list of default selection values. |
| options | yes | - | The list of options to choose from. |
| limit | no | `false` | The limit of the multiple selection list. |

#### list/multi_list Options

Properties:

| Property | Required | Description |
|:---------|:---------|:---------------------------------|
| key | yes | The message shown in the UI. |
| value | yes | Unique identifier for the value. |

### Multiple Templates

You can define multiple templates in the `.git-czrc` file, separated by `---`:
You can define multiple templates in the `.czrc` file, separated by `---`:

```yaml
name: angular-template
items:
- name: scope
desc: "Scope. Could be anything specifying place of the commit change:"
type: input
# ...
# ...
format: "{{.type}}{{with .scope}}({{.}}){{end}}: {{.subject}}{{with .body}}\n\n{{.}}{{end}}{{with .footer}}\n\n{{.}}{{end}}"`

---

name: my-template
items:
- name: scope
desc: "Scope. Could be anything specifying place of the commit change:"
type: input
# ...
# ...
format: "{{.type}}{{with .scope}}({{.}}){{end}}: {{.subject}}{{with .body}}\n\n{{.}}{{end}}{{with .footer}}\n\n{{.}}{{end}}"`
```

Expand Down
3 changes: 2 additions & 1 deletion internal/config/default.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package config

const DefaultCommitTemplate = `---
version: v2
name: default
default: true
items:
Expand Down Expand Up @@ -32,11 +31,13 @@ items:
group: page2
label: "Scope. What is the scope of this change? (class or file name):"
type: string
trim: true
- name: subject
group: page2
label: "Subject. Write a short and imperative summary of the code change (lower case and no period):"
type: string
required: true
trim: true
- name: body
group: page3
label: "Body. Provide additional contextual information about the code changes:"
Expand Down
2 changes: 1 addition & 1 deletion internal/errors/missing.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func (e MissingErr) Error() string {
if e.name == "" {
return fmt.Sprintf("missing required field `%s`", e.field)
}
return fmt.Sprintf("'%s' missing required field: %s", e.name, e.field)
return fmt.Sprintf("item '%s' missing required field: %s", e.name, e.field)
}

func NewMissingErr(field string, name ...string) error {
Expand Down
4 changes: 2 additions & 2 deletions internal/parameter/boolean/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Param struct {
DefaultValue bool `yaml:"default_value" json:"default_value" mapstructure:"default_value"`
}

func (p Param) Render() huh.Field {
func (p *Param) Render() {
param := huh.NewConfirm().Key(p.Name).
Title(p.Label)

Expand All @@ -22,5 +22,5 @@ func (p Param) Render() huh.Field {

param.Value(&p.DefaultValue)

return param
p.Field = param
}
4 changes: 2 additions & 2 deletions internal/parameter/integer/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Param struct {
Max *int `yaml:"max" json:"max" mapstructure:"max"`
}

func (p Param) Render() huh.Field {
func (p *Param) Render() {
param := huh.NewInput().Key(p.Name).
Title(p.Label)

Expand All @@ -40,5 +40,5 @@ func (p Param) Render() huh.Field {
if len(group) > 0 {
param.Validate(validators.Group(group...))
}
return param
p.Field = param
}
6 changes: 3 additions & 3 deletions internal/parameter/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ type Param struct {
Required bool `yaml:"required" json:"required" mapstructure:"required"`
}

func (p Param) Validate() []error {
func (p *Param) Validate() []error {
errs := p.Parameter.Validate()
if len(p.Options) < 1 {
errs = append(errs, errors.NewMissingErr("options", p.Name))
}
return errs
}

func (p Param) Render() huh.Field {
func (p *Param) Render() {
param := huh.NewSelect[string]().Key(p.Name).
Options(p.Options...).
Title(p.Label)
Expand All @@ -42,5 +42,5 @@ func (p Param) Render() huh.Field {
if len(group) > 0 {
param.Validate(validators.Group(group...))
}
return param
p.Field = param
}
6 changes: 3 additions & 3 deletions internal/parameter/multilist/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ type Param struct {
Limit *int `yaml:"limit" json:"limit" mapstructure:"limit"`
}

func (p Param) Validate() []error {
func (p *Param) Validate() []error {
errs := p.Parameter.Validate()
if len(p.Options) < 1 {
errs = append(errs, errors.NewMissingErr("options", p.Name))
}
return errs
}

func (p Param) Render() huh.Field {
func (p *Param) Render() {
param := huh.NewMultiSelect[string]().Key(p.Name).
Options(p.Options...).
Title(p.Label)
Expand All @@ -47,5 +47,5 @@ func (p Param) Render() huh.Field {
param.Validate(validators.Group(group...))
}

return param
p.Field = param
}
Loading