Skip to content

Commit

Permalink
Add logic of protecting branches
Browse files Browse the repository at this point in the history
A user may have several branches in the repository in which he/she can't
do modifications. For instance, it is a case when there is `master` and
`develop` branches. These branches are protected ones and Elegant Git
allows to:
- configure global and local branches that should be protected
- disable removing of the protected branches
- disable changing the history of protected branches

This is implemented by
1. aborting the execution of `amend-work`, `deliver-work`, `polish-work`,
`save-work` if any of the commands is run against a protected branch
2. skipping the protected branches in `prune-repository` command
3. asking for the protected branches in the `acquire-git` and
   `acquire-repository` commands

Also, the documentation is updated accordingly.
  • Loading branch information
extsoft committed May 25, 2020
1 parent 6c44e24 commit db44c5c
Show file tree
Hide file tree
Showing 17 changed files with 244 additions and 96 deletions.
9 changes: 5 additions & 4 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
112 changes: 67 additions & 45 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -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 <key> <value>`.

The global configuration invokes by [`git elegant acquire-git`](commands.md#acquire-git) and uses
`git config --global <key> <value>` for Git configuration.
repository by using `git config --local <key> <value>`
- the global configuration invokes by running [`git elegant acquire-git`](commands.md#acquire-git)
and uses `git config --global <key> <value>` 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.<command>" "elegant <command>"` (`i`) for each Elegant
The configuration is a call of `git config "alias.<command>" "elegant <command>"` [`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.
25 changes: 15 additions & 10 deletions libexec/git-elegant-acquire-git
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<MESSAGE
Expand Down Expand Up @@ -73,17 +74,21 @@ MESSAGE
exit 0
fi
fi
basics-configuration --global -- user_name user_email core_editor
basics-configuration --global -- \
user_name \
user_email \
core_editor \
protected_branches
standards-configuration --global \
core_comment \
apply_whitespace \
fetch_prune \
fetch_pruneTags \
core_autocrlf_darwinlinux \
core_autocrlf_windows pull_rebase \
rebase_autoStash \
credential_helper_darwin \
acquired
core_comment \
apply_whitespace \
fetch_prune \
fetch_pruneTags \
core_autocrlf_darwinlinux \
core_autocrlf_windows pull_rebase \
rebase_autoStash \
credential_helper_darwin \
acquired
aliases-removing --global
aliases-configuration --global $(git elegant show-commands)
}
28 changes: 18 additions & 10 deletions libexec/git-elegant-acquire-repository
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,28 @@ MESSAGE

default() {
source ${BINS}/plugins/configuration
source ${BINS}/plugins/configuration-protected-branches
if $(is-acquired) ; then
basics-configuration --local user_name user_email
basics-configuration --local \
user_name \
user_email \
protected_branches
aliases-removing --local
else
basics-configuration --local user_name user_email core_editor
basics-configuration --local \
user_name \
user_email \
core_editor \
protected_branches
standards-configuration --local \
core_comment \
apply_whitespace \
fetch_prune \
fetch_pruneTags \
core_autocrlf_darwinlinux \
core_autocrlf_windows pull_rebase \
rebase_autoStash \
credential_helper_darwin
core_comment \
apply_whitespace \
fetch_prune \
fetch_pruneTags \
core_autocrlf_darwinlinux \
core_autocrlf_windows pull_rebase \
rebase_autoStash \
credential_helper_darwin
aliases-removing --local
aliases-configuration --local $(git elegant show-commands)
fi
Expand Down
12 changes: 7 additions & 5 deletions libexec/git-elegant-amend-work
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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
Expand All @@ -32,10 +32,12 @@ MESSAGE
}

default(){
local BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$BRANCH" == "master" ]]; then
error-box "No commits to 'master' branch. Please read more on ${__site}"
error-box "Try 'git elegant start-work' prior to retrying this command."
local current_branch=$(git rev-parse --abbrev-ref HEAD)
source ${BINS}/plugins/configuration-protected-branches
if is-branch-protected ${current_branch} ; then
error-box "No direct commits to the protected '${current_branch}' branch."
error-text "Please read more on ${__site}."
error-text "Run 'git elegant start-work' prior to retrying this command."
exit 42
fi
git-verbose add --interactive
Expand Down
8 changes: 6 additions & 2 deletions libexec/git-elegant-deliver-work
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ command-description() {
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
Expand Down Expand Up @@ -79,8 +81,10 @@ MESSAGE

default() {
local branch=$(git rev-parse --abbrev-ref HEAD)
if [[ "${branch}" == "master" ]]; then
error-box "No pushes to 'master' branch. Please read more on ${__site}"
source ${BINS}/plugins/configuration-protected-branches
if is-branch-protected ${branch} ; then
error-box "The push of the protected '${branch}' branch is prohibited."
error-text "Consider using 'git elegant accept-work' or use plain 'git push'."
exit 42
fi
stash-pipe --deliver-work-logic ${branch} "${@}"
Expand Down
10 changes: 6 additions & 4 deletions libexec/git-elegant-polish-work
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,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
Expand All @@ -38,8 +37,11 @@ MESSAGE
}

default() {
if [[ $(git rev-parse --abbrev-ref HEAD) == "master" ]]; then
error-box "'master' branch history can't be rewritten. Please read more on ${__site}"
local current_branch=$(git rev-parse --abbrev-ref HEAD)
source ${BINS}/plugins/configuration-protected-branches
if is-branch-protected ${current_branch}; then
error-box "The protected '${current_branch}' branch history can't be rewritten."
error-text "Please read more on ${__site}."
exit 42
fi
source ${BINS}/plugins/state
Expand Down
3 changes: 2 additions & 1 deletion libexec/git-elegant-prune-repository
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ default() {
git-verbose fetch --all || info-text "As the remotes can't be fetched, the current local version is used."
git-verbose rebase
fi
source ${BINS}/plugins/configuration-protected-branches
for branch in $(git for-each-ref --format "%(refname:short)" refs/heads); do
if [[ ${branch} == ${DEFAULT_BRANCH} ]]; then continue; fi
if is-branch-protected ${branch} ; then continue; fi
if [[ -n $(git config --get branch.${branch}.merge) ]]; then
if is-there-upstream-for ${branch}; then
# the branch has existing upstream; keep it
Expand Down
12 changes: 7 additions & 5 deletions libexec/git-elegant-save-work
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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.
Expand All @@ -34,10 +34,12 @@ MESSAGE
}

default(){
local BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$BRANCH" == "master" ]]; then
error-box "No commits to 'master' branch. Please read more on ${__site}"
error-box "Try 'git elegant start-work' prior to retrying this command."
local current_branch=$(git rev-parse --abbrev-ref HEAD)
source ${BINS}/plugins/configuration-protected-branches
if is-branch-protected ${current_branch} ; then
error-box "No direct commits to the protected '${current_branch}' branch."
error-text "Please read more on ${__site}."
error-text "Run 'git elegant start-work' prior to retrying this command."
exit 42
fi
git-verbose add --interactive
Expand Down
21 changes: 21 additions & 0 deletions libexec/plugins/configuration-protected-branches
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
## This plugin is responsible for handling protected branches logic.
protected_branches_key="elegant-git.protected-branches"
protected_branches_default="master"
protected_branches_message="What are protected branches (split with space)?"

protected-branches() {
# usage : protected-branches
# result: "master other-branch"
git config ${protected_branches_key} || echo ${protected_branches_default}
}

is-branch-protected() {
# usage : is-branch-protected <my-branch-name>
# 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
}
3 changes: 3 additions & 0 deletions tests/git-elegant-acquire-git.bats
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ teardown() {
[[ ${lines[@]} =~ "==>> git config --global user.email [email protected]" ]]
[[ ${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" {
Expand Down Expand Up @@ -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" ]]
Expand Down
Loading

0 comments on commit db44c5c

Please sign in to comment.