diff --git a/docs/commands.md b/docs/commands.md index a754d98..5cd8eac 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -142,7 +142,7 @@ Amends the last commit by incorporating the current state of the repository. The command provides full control of what modifications should be included by starting an interactive commit process. -The command doesn't allow to modify the history of the default development branch. +The command doesn't allow to modify the history of the protected branches. Approximate commands flow is ```bash @@ -180,6 +180,8 @@ usage: git elegant deliver-work [branch-name] Rebases the head of the remote default development branch into the current one and pushes the branch to the remote default upstream repository. +The command doesn't allow to push a protected branch. + A `[branch-name]` name is a name of a remote-tracking branch in the remote default upstream repository. By default, it is equal to the name of the current local branch. However, if HEAD has a configured remote-tracking branch, it will @@ -286,8 +288,7 @@ default development branches) and runs interactive rebase against them. If there is a rebase in progress, the command will pick it up and continue. -The command doesn't allow to modify the history of the default development -branch. +The command doesn't allow to modify the history of the protected branches. The command uses stash pipe to preserve the current Git state prior to execution and restore after. This means that the uncommitted local @@ -393,7 +394,7 @@ Saves available modifications as a new commit. The command provides full control of what modifications should be included by starting an interactive commit process. -The command doesn't allow to add a commit to the default development branch. +The command doesn't allow to modify the history of the protected branches. If there are trailing whitespaces in the modifications, the commit is rejected. diff --git a/docs/configuration.md b/docs/configuration.md index ce7451c..ceedf93 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,68 +1,90 @@ # Approach + Elegant Git aims to standardize how a work environment should be configured. It operates several levels of configurations (see below) that can be applied to a Git repository (local configuration) -and/or to a Git installation globally (global configuration). +and/or to a Git installation globally (global configuration). So, -The local configuration applies by running +- the local configuration applies by running [`git elegant acquire-repository`](commands.md#acquire-repository) and configures current Git -repository by using `git config --local `. - -The global configuration invokes by [`git elegant acquire-git`](commands.md#acquire-git) and uses -`git config --global ` for Git configuration. +repository by using `git config --local ` +- the global configuration invokes by running [`git elegant acquire-git`](commands.md#acquire-git) +and uses `git config --global ` for Git configuration If you've applied a global configuration, there is no sense to repeat some options for a local one. That's why the following markers explain how each particular option will be configured: -- `b` - configures for both configurations -- `l` - configures only for a local configuration -- `g` - configures only for a global configuration -- `i` - if a global configuration is applied, an option isn't used in local configuration; +- [`b`] - configures for both configurations +- [`l`] - configures only for a local configuration +- [`g`] - configures only for a global configuration +- [`i`] - if a global configuration is applied, this option isn't used in local configuration; otherwise, uses in local configuration -# Basics -The basics configuration configures the following options `git config` options: - -1. `user.name` (`b`) -2. `user.email` (`b`) -3. `core.editor` (`i`) - -During the configuration, you will be asked to provide appropriate values. - -# Standards -The standards configuration configures a set of the `git config` options which both handle -OS-specific configurations and add specific options for the correct execution of Elegant Git -commands. It consists of - -1. setting `core.commentChar` (`i`) to `|` enables commit messages starting from `#` -2. setting `apply.whitespace` (`i`) to `fix` removes whitespaces when applying a patch -3. setting `fetch.prune` (`i`) to `true` keeps remote-tracking references up-to-date -4. setting `fetch.pruneTags` (`i`) to `false` does not remove tags until you specify it explicitly -(`git fetch --tags`) -5. setting `core.autocrlf` (`i`) to either `input` on MacOS/Linux or `true` on Windows solves issues with -line endings -6. setting `pull.rebase` (`i`) to `true` uses `rebase` when `git pull` -7. setting `rebase.autoStash` (`i`) to `false` uses `autostash` never when `git rebase` -8. setting `credential.helper` (`i`) to `osxkeychain` on MacOS configures default credentials storage -9. setting `elegant.acquired` (`g`) to `true` identifies that Elegant Git global configuration is applied - -# Aliases +Also, there are defined [the custom configuration keys](#custom-keys) in addition to +[the standard `git config` options](https://git-scm.com/docs/git-config). These keys will be configured +automatically during `acquire-git` or `acquire-repository` execution, so, you don't need to set them +manually. + +# Level: Basics + +The basics configuration sets the mandatory options for the correct user-focused operation of Git and +Elegant Git. During the configuration, you will be asked to provide appropriate values. Furthermore, +if you `acquire-repository`, it proposes defaults that are set by `acquire-git`. The basics includes: + +1. setting your full name usign `user.name` [`b`] +2. setting your email usign `user.email` [`b`] +3. setting a default editor using `core.editor` [`i`] +4. setting protected branches using `elegant-git.protected-branches` [`b`] + +# Level: Standards + +The standards configuration adopts the Git setting for painless and user-oriented commands execution +for both Git and Elegant Git. It takes into account OS-specific stuff while configuring specific +options. All Git options in this configuration have the defined values and any changes to them may +affect the designed behavior of the Elegant Git. However, it should not degrade your Git-related +experience. So, the following configuration is applied automatically: + +1. `core.commentChar |` [`i`] enables lines in commit messages starting from `#` (`|` prefixes lines that should be ignored) +2. `apply.whitespace fix` [`i`] removes whitespaces when applying a patch +3. `fetch.prune true` [`i`] keeps remote-tracking references up-to-date +4. `fetch.pruneTags false` [`i`] does not remove tags while fetching until you specify it explicitly with +`git fetch --tags` +5. `core.autocrlf input` [`i`] solves issues with line endings on either MacOS/Linux with `input` or +Windows with `true` +6. `pull.rebase true` [`i`] uses `rebase` when `git pull` +7. `rebase.autoStash false` [`i`] don't use `autostash` when `git rebase` +8. `credential.helper osxkeychain` [`i`] configures default credentials storage on MacOS only +9. `elegant.acquired true` [`g`] identifies that Elegant Git global configuration is applied + +# Level: Aliases + In order to make Elegant Git command like a native Git command, each Elegant Git command will have an appropriate alias like `git elegant save-work` will become `git save-work`. This should significantly improve user experience. -The configuration is a call of `git config "alias." "elegant "` (`i`) for each Elegant +The configuration is a call of `git config "alias." "elegant "` [`i`] for each Elegant Git command. -# Signature +# Level: Signature + This configuration aims to say Git how to sign commits, tags, and other objects you create. It starts after all other configurations. In the beginning, all available signing keys will be shown. Then, you need to choose the key that will be used to make signatures. If the key is provided, the configuration triggers, otherwise, it does not apply. The signing configuration consists of -1. setting `user.signingkey` (`l`) to a provided value -2. setting `gpg.program` (`l`) to a full path of `gpg` program -3. setting `commit.gpgsign` (`l`) to `true` -4. setting `tag.forceSignAnnotated` (`l`) to `true` -5. setting `tag.gpgSign` (`l`) to `true` +1. setting `user.signingkey` [`l`] to a provided value +2. setting `gpg.program` [`l`] to a full path of `gpg` program +3. setting `commit.gpgsign` [`l`] to `true` +4. setting `tag.forceSignAnnotated` [`l`] to `true` +5. setting `tag.gpgSign` [`l`] to `true` + +For now, only `gpg` is supported. If you need other tools, please [create a new feature request][https://github.com/bees-hive/elegant-git/issues/new/choose]. + +# Custom keys + +The Elegant Git configuration keys: -For now, only `gpg` is supported. If you need other tools, please [create a new feature request](https://github.com/bees-hive/elegant-git/issues/new/choose). +- `elegant-git.protected-branches` defines the protected branches (if there are multiple values, they +should be separarated with space). By default, `master` branch treats as protected. The "protected" +means that Elegant Git commands for a branch state modification (such as `save-work`, `polish-work`, +etc.) are prohibited to work if the current branch is protected. Also, the protected branches cannot +be removed while running Elegant Git commands for serving a repository. diff --git a/libexec/git-elegant-acquire-git b/libexec/git-elegant-acquire-git index 02e496f..9eb37b2 100755 --- a/libexec/git-elegant-acquire-git +++ b/libexec/git-elegant-acquire-git @@ -30,6 +30,7 @@ MESSAGE default() { source ${BINS}/plugins/configuration + source ${BINS}/plugins/configuration-protected-branches if ! $(is-acquired) ; then info-box "Thank you for installing Elegant Git! Let's configure it..." cat < + # result: 0 or 1 + _error-if-empty "${1}" "Please give a branch name." + if [[ $(protected-branches) =~ ((^${1} )|(${1}$)|( ${1} )) ]]; then + return 0 + fi + return 1 +} diff --git a/tests/git-elegant-acquire-git.bats b/tests/git-elegant-acquire-git.bats index c9b3097..b549d20 100644 --- a/tests/git-elegant-acquire-git.bats +++ b/tests/git-elegant-acquire-git.bats @@ -30,6 +30,8 @@ teardown() { [[ ${lines[@]} =~ "==>> git config --global user.email elegant-git@example.com" ]] [[ ${lines[@]} =~ "Please specify a command to start the editor. {vi}: " ]] [[ ${lines[@]} =~ "==>> git config --global core.editor vi" ]] + [[ ${lines[@]} =~ "What are protected branches (split with space)?" ]] + [[ ${lines[@]} =~ "==>> git config --global elegant-git.protected-branches master" ]] } @test "'acquire-git': standards are configured as expected on Windows" { @@ -109,6 +111,7 @@ teardown() { repo git config --global user.name aaaa repo git config --global user.email aaaa repo git config --global core.editor aaaa + repo git config --global elegant-git.protected-branches master check git-elegant acquire-git [[ ${status} -eq 0 ]] [[ ${lines[@]} =~ "==>> git config --global user.name aaaa" ]] diff --git a/tests/git-elegant-acquire-repository.bats b/tests/git-elegant-acquire-repository.bats index 61f4da9..5986db1 100644 --- a/tests/git-elegant-acquire-repository.bats +++ b/tests/git-elegant-acquire-repository.bats @@ -21,7 +21,7 @@ teardown() { [[ ${status} -eq 0 ]] } -@test "'acquire-repository': basics are configured as expected" { +@test "'acquire-repository': basics with default values are configured as expected" { check git-elegant acquire-repository [[ ${lines[@]} =~ "What is your user name? {Elegant Git}: " ]] [[ ${lines[@]} =~ "==>> git config --local user.name Elegant Git" ]] @@ -29,6 +29,24 @@ teardown() { [[ ${lines[@]} =~ "==>> git config --local user.email elegant-git@example.com" ]] [[ ${lines[@]} =~ "Please specify a command to start the editor. {vi}: " ]] [[ ${lines[@]} =~ "==>> git config --local core.editor vi" ]] + [[ ${lines[@]} =~ "What are protected branches (split with space)? {master}:" ]] + [[ ${lines[@]} =~ "==>> git config --local elegant-git.protected-branches master" ]] +} + +@test "'acquire-repository': basics with user-provided values are configured as expected" { + read-answer "The User" + read-answer "the@email" + read-answer "someeditor" + read-answer "a b" + check git-elegant acquire-repository + [[ ${lines[@]} =~ "What is your user name? {Elegant Git}: " ]] + [[ ${lines[@]} =~ "==>> git config --local user.name The User" ]] + [[ ${lines[@]} =~ "What is your user email? {elegant-git@example.com}: " ]] + [[ ${lines[@]} =~ "==>> git config --local user.email the@email" ]] + [[ ${lines[@]} =~ "Please specify a command to start the editor. {vi}: " ]] + [[ ${lines[@]} =~ "==>> git config --local core.editor someeditor" ]] + [[ ${lines[@]} =~ "What are protected branches (split with space)? {master}:" ]] + [[ ${lines[@]} =~ "==>> git config --local elegant-git.protected-branches a b" ]] } @test "'acquire-repository': standards are configured as expected on Windows" { @@ -103,6 +121,8 @@ teardown() { [[ ${lines[@]} =~ "==>> git config --local user.name Elegant Git" ]] [[ ${lines[@]} =~ "What is your user email? {elegant-git@example.com}: " ]] [[ ${lines[@]} =~ "==>> git config --local user.email elegant-git@example.com" ]] + [[ ${lines[@]} =~ "What are protected branches (split with space)? {master}: " ]] + [[ ${lines[@]} =~ "==>> git config --local elegant-git.protected-branches master" ]] [[ ! ${lines[@]} =~ "Please specify a command to start the editor. {vi}: " ]] [[ ! ${lines[@]} =~ "==>> git config --local core.editor vi" ]] [[ ! ${lines[@]} =~ "==>> git config --local core.commentChar |" ]] @@ -114,6 +134,7 @@ teardown() { read-answer "The User" read-answer "the@email" read-answer "someeditor" + read-answer "master" read-answer "thekey" fake-pass "gpg --list-secret-keys --keyid-format long the@email" "some dummy keys" check git-elegant acquire-repository diff --git a/tests/git-elegant-amend-work.bats b/tests/git-elegant-amend-work.bats index e5c9f55..ea9b855 100644 --- a/tests/git-elegant-amend-work.bats +++ b/tests/git-elegant-amend-work.bats @@ -23,9 +23,21 @@ teardown() { [[ ${status} -eq 0 ]] } -@test "'amend-work': exit code is 42 when current local branch is master" { + +@test "'amend-work': exit code is 42 when the command is run against default protected branch" { + check git-elegant amend-work + [[ ${status} -eq 42 ]] + [[ ${lines[@]} =~ "No direct commits to the protected 'master' branch." ]] + [[ ${lines[@]} =~ "Please read more on https://elegant-git.bees-hive.org." ]] + [[ ${lines[@]} =~ "Run 'git elegant start-work' prior to retrying this command." ]] +} + +@test "'amend-work': exit code is 42 when the command is run against custom protected branch" { + repo "git config --local elegant-git.protected-branches \"master some\"" + repo "git checkout -b some" check git-elegant amend-work [[ ${status} -eq 42 ]] - [[ ${lines[@]} =~ "== No commits to 'master' branch. Please read more on https://elegant-git.bees-hive.org ==" ]] - [[ ${lines[@]} =~ "== Try 'git elegant start-work' prior to retrying this command. ==" ]] + [[ ${lines[@]} =~ "No direct commits to the protected 'some' branch." ]] + [[ ${lines[@]} =~ "Please read more on https://elegant-git.bees-hive.org." ]] + [[ ${lines[@]} =~ "Run 'git elegant start-work' prior to retrying this command." ]] } diff --git a/tests/git-elegant-deliver-work.bats b/tests/git-elegant-deliver-work.bats index 6186a28..fc5737e 100644 --- a/tests/git-elegant-deliver-work.bats +++ b/tests/git-elegant-deliver-work.bats @@ -32,10 +32,20 @@ teardown() { [[ ${lines[@]} =~ "git push --set-upstream --force origin feature1:feature2" ]] } -@test "'deliver-work': exit code is 42 when current local branch is master" { +@test "'deliver-work': exit code is 42 when the command is run against default protected branch" { check git-elegant deliver-work [[ ${status} -eq 42 ]] - [[ ${lines[1]} = "== No pushes to 'master' branch. Please read more on https://elegant-git.bees-hive.org ==" ]] + [[ ${lines[@]} =~ "The push of the protected 'master' branch is prohibited." ]] + [[ ${lines[@]} =~ "Consider using 'git elegant accept-work' or use plain 'git push'." ]] +} + +@test "'deliver-work': exit code is 42 when the command is run against custom protected branch" { + repo "git config --local elegant-git.protected-branches \"master some\"" + repo "git checkout -b some" + check git-elegant deliver-work + [[ ${status} -eq 42 ]] + [[ ${lines[@]} =~ "The push of the protected 'some' branch is prohibited." ]] + [[ ${lines[@]} =~ "Consider using 'git elegant accept-work' or use plain 'git push'." ]] } @test "'deliver-work': use stash pipe if there are uncommitted changes" { diff --git a/tests/git-elegant-polish-work.bats b/tests/git-elegant-polish-work.bats index 19c1d44..1949e01 100644 --- a/tests/git-elegant-polish-work.bats +++ b/tests/git-elegant-polish-work.bats @@ -13,9 +13,20 @@ teardown() { fake-clean } -@test "'polish-work': exit code is 42 when the command is run against 'master' branch" { +@test "'polish-work': exit code is 42 when the command is run against default protected branch" { check git-elegant polish-work [[ ${status} -eq 42 ]] + [[ ${lines[@]} =~ "The protected 'master' branch history can't be rewritten." ]] + [[ ${lines[@]} =~ "Please read more on https://elegant-git.bees-hive.org." ]] +} + +@test "'polish-work': exit code is 42 when the command is run against custom protected branch" { + repo "git config --local elegant-git.protected-branches \"master some\"" + repo "git checkout -b some" + check git-elegant polish-work + [[ ${status} -eq 42 ]] + [[ ${lines[@]} =~ "The protected 'some' branch history can't be rewritten." ]] + [[ ${lines[@]} =~ "Please read more on https://elegant-git.bees-hive.org." ]] } @test "'polish-work': a rebase process doesn't start when there are no new commits" { diff --git a/tests/git-elegant-prune-repository.bats b/tests/git-elegant-prune-repository.bats index ecfb51b..a2e2748 100644 --- a/tests/git-elegant-prune-repository.bats +++ b/tests/git-elegant-prune-repository.bats @@ -71,3 +71,15 @@ teardown() { [[ ${status} -eq 0 ]] [[ ! ${lines[@]} =~ "git branch --delete --force upstream" ]] } + +@test "'prune-repository': keeps protected branches" { + repo "git config --local elegant-git.protected-branches \"master some\"" + repo "git checkout -b some" + repo "git checkout -b equal-to-master" + repo "git checkout master" + check git-elegant prune-repository + [[ ${status} -eq 0 ]] + [[ ${lines[@]} =~ "git branch --delete --force equal-to-master" ]] + [[ ! ${lines[@]} =~ "git branch --delete --force master" ]] + [[ ! ${lines[@]} =~ "git branch --delete --force some" ]] +} diff --git a/tests/git-elegant-save-work.bats b/tests/git-elegant-save-work.bats index 8672992..3c07cce 100644 --- a/tests/git-elegant-save-work.bats +++ b/tests/git-elegant-save-work.bats @@ -23,9 +23,20 @@ teardown() { [[ ${status} -eq 0 ]] } -@test "'save-work': exit code is 42 when current local branch is master" { +@test "'save-work': exit code is 42 when the command is run against default protected branch" { check git-elegant save-work [[ ${status} -eq 42 ]] - [[ ${lines[@]} =~ "== No commits to 'master' branch. Please read more on https://elegant-git.bees-hive.org ==" ]] - [[ ${lines[@]} =~ "== Try 'git elegant start-work' prior to retrying this command. ==" ]] + [[ ${lines[@]} =~ "No direct commits to the protected 'master' branch." ]] + [[ ${lines[@]} =~ "Please read more on https://elegant-git.bees-hive.org." ]] + [[ ${lines[@]} =~ "Run 'git elegant start-work' prior to retrying this command." ]] +} + +@test "'save-work': exit code is 42 when the command is run against custom protected branch" { + repo "git config --local elegant-git.protected-branches \"master some\"" + repo "git checkout -b some" + check git-elegant save-work + [[ ${status} -eq 42 ]] + [[ ${lines[@]} =~ "No direct commits to the protected 'some' branch." ]] + [[ ${lines[@]} =~ "Please read more on https://elegant-git.bees-hive.org." ]] + [[ ${lines[@]} =~ "Run 'git elegant start-work' prior to retrying this command." ]] }