From ebe77391c67c5fa6314697c01af103e3722b917c Mon Sep 17 00:00:00 2001 From: Stefan Zweifel Date: Sun, 27 Feb 2022 07:52:17 +0100 Subject: [PATCH] Add `create_branch` option to force create a new branch (#203) * Add create_branch option * Checkout new branch if create_branch input is true * Add tests * Update README --- README.md | 28 ++-- action.yml | 5 +- entrypoint.sh | 14 +- tests/git-auto-commit.bats | 257 +++++++++++++++++++++++++++++++++++++ 4 files changed, 287 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f18675b..e70737e 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,16 @@ The following is an extended example with all possible options available for thi ```yaml - uses: stefanzweifel/git-auto-commit-action@v4 with: - # Optional, but recommended + # Optional. Commit message for the created commit. # Defaults to "Apply automatic changes" commit_message: Automated Change - # Optional branch name where commit should be pushed to. - # Defaults to the current branch. + # Optional. Local and remote branch name where commit is going to be pushed + # to. Defaults to the current branch. + # You might need to set `create_branch: true` if the branch does not exist. branch: feature-123 - # Optional. Used by `git-commit`. + # Optional. Options used by `git-commit`. # See https://git-scm.com/docs/git-commit#_options commit_options: '--no-verify --signoff' @@ -47,8 +48,8 @@ The following is an extended example with all possible options available for thi # - https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec file_pattern: src/*.js tests/*.js *.php - # Optional local file path to the repository - # Defaults to the root of the repository + # Optional. Local file path to the repository. + # Defaults to the root of the repository. repository: . # Optional commit user and author settings @@ -56,19 +57,19 @@ The following is an extended example with all possible options available for thi commit_user_email: my-github-actions-bot@example.org # defaults to "actions@github.com" commit_author: Author # defaults to author of the commit that triggered the run - # Optional tag message - # Action will create and push a new tag to the remote repository and the defined branch + # Optional. Tag name being created in the local repository and + # pushed to remtoe repository and defined branch. tagging_message: 'v1.0.0' - # Optional. Used by `git-status` - # See https://git-scm.com/docs/git-status#_options + # Optional. Option used by `git-status` to determine if the repository is + # dirty. See https://git-scm.com/docs/git-status#_options status_options: '--untracked-files=no' - # Optional. Used by `git-add` + # Optional. Options used by `git-add`. # See https://git-scm.com/docs/git-add#_options add_options: '-u' - # Optional. Used by `git-push` + # Optional. Options used by `git-push`. # See https://git-scm.com/docs/git-push#_options push_options: '--force' @@ -84,6 +85,9 @@ The following is an extended example with all possible options available for thi # Optional. Prevents the shell from expanding filenames. # Details: https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html disable_globbing: true + + # Optional. Create given branch name in local and remote repository. + create_branch: true ``` Please note that the Action depends on `bash`. If you're using the Action in a job in combination with a custom Docker container, make sure that `bash` is installed. diff --git a/action.yml b/action.yml index 03a8557..0c30824 100644 --- a/action.yml +++ b/action.yml @@ -25,7 +25,7 @@ inputs: required: false default: '' file_pattern: - description: File pattern used for `git add`. For example `src/\*.js` + description: File pattern used for `git add`. For example `src/*.js` required: false default: '.' repository: @@ -67,6 +67,9 @@ inputs: disable_globbing: description: Stop the shell from expanding filenames (https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html) default: false + create_branch: + description: Create new branch with the name of `branch`-input in local and remote repository, if it doesn't exist yet. + default: false outputs: changes_detected: diff --git a/entrypoint.sh b/entrypoint.sh index 3b0c85e..dee54a9 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -48,19 +48,25 @@ _switch_to_branch() { echo "INPUT_BRANCH value: $INPUT_BRANCH"; # Fetch remote to make sure that repo can be switched to the right branch. - if "$INPUT_SKIP_FETCH"; then echo "::debug::git-fetch has not been executed"; else git fetch --depth=1; fi + # If `skip_checkout`-input is true, skip the entire checkout step. if "$INPUT_SKIP_CHECKOUT"; then echo "::debug::git-checkout has not been executed"; else - # Switch to branch from current Workflow run - # shellcheck disable=SC2086 - git checkout $INPUT_BRANCH --; + # Create new local branch if `create_branch`-input is true + if "$INPUT_CREATE_BRANCH"; then + # shellcheck disable=SC2086 + git checkout -B $INPUT_BRANCH --; + else + # Switch to branch from current Workflow run + # shellcheck disable=SC2086 + git checkout $INPUT_BRANCH --; + fi fi } diff --git a/tests/git-auto-commit.bats b/tests/git-auto-commit.bats index 2250496..da25586 100644 --- a/tests/git-auto-commit.bats +++ b/tests/git-auto-commit.bats @@ -26,6 +26,7 @@ setup() { export INPUT_SKIP_FETCH=false export INPUT_SKIP_CHECKOUT=false export INPUT_DISABLE_GLOBBING=false + export INPUT_CREATE_BRANCH=false # Configure Git if [[ -z $(git config user.name) ]]; then @@ -533,3 +534,259 @@ git_auto_commit() { assert_line "::set-output name=changes_detected::true" assert_line "::debug::Push commit to remote branch dev" } + +@test "script fails to push commit to new branch that does not exist yet" { + INPUT_BRANCH="not-existend-branch" + INPUT_CREATE_BRANCH=false + + run git branch + refute_line --partial "not-existend-branch" + + run git branch -r + refute_line --partial "origin/not-existend-branch" + + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-{1,2,3}.txt + + run git_auto_commit + + assert_failure + + assert_line "INPUT_REPOSITORY value: ${INPUT_REPOSITORY}" + assert_line "::set-output name=changes_detected::true" + assert_line "INPUT_BRANCH value: not-existend-branch" + assert_line "fatal: invalid reference: not-existend-branch" + + run git branch + refute_line --partial "not-existend-branch" + + run git branch -r + refute_line --partial "origin/not-existend-branch" +} + +@test "It creates new local branch and pushes the new branch to remote" { + INPUT_BRANCH="not-existend-branch" + INPUT_CREATE_BRANCH=true + + run git branch + refute_line --partial "not-existend-branch" + + run git branch -r + refute_line --partial "origin/not-existend-branch" + + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-{1,2,3}.txt + + run git_auto_commit + + assert_success + + assert_line "INPUT_REPOSITORY value: ${INPUT_REPOSITORY}" + assert_line "::set-output name=changes_detected::true" + assert_line -e "::set-output name=commit_hash::[0-9a-f]{40}$" + assert_line "INPUT_BRANCH value: not-existend-branch" + assert_line "INPUT_FILE_PATTERN: ." + assert_line "INPUT_COMMIT_OPTIONS: " + assert_line "::debug::Apply commit options " + assert_line "INPUT_TAGGING_MESSAGE: " + assert_line "No tagging message supplied. No tag will be added." + assert_line "INPUT_PUSH_OPTIONS: " + assert_line "::debug::Apply push options " + assert_line "::debug::Push commit to remote branch not-existend-branch" + + run git branch + assert_line --partial "not-existend-branch" + + run git branch -r + assert_line --partial "origin/not-existend-branch" +} + +@test "it does not create new local branch if local branch already exists" { + + git checkout -b not-existend-remote-branch + git checkout master + + INPUT_BRANCH="not-existend-remote-branch" + INPUT_CREATE_BRANCH=true + + run git branch + assert_line --partial "not-existend-remote-branch" + + run git branch -r + refute_line --partial "origin/not-existend-remote-branch" + + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-{1,2,3}.txt + + run git_auto_commit + + assert_success + + assert_line "INPUT_REPOSITORY value: ${INPUT_REPOSITORY}" + assert_line "::set-output name=changes_detected::true" + assert_line -e "::set-output name=commit_hash::[0-9a-f]{40}$" + assert_line "INPUT_BRANCH value: not-existend-remote-branch" + assert_line "INPUT_FILE_PATTERN: ." + assert_line "INPUT_COMMIT_OPTIONS: " + assert_line "::debug::Apply commit options " + assert_line "INPUT_TAGGING_MESSAGE: " + assert_line "No tagging message supplied. No tag will be added." + assert_line "INPUT_PUSH_OPTIONS: " + assert_line "::debug::Apply push options " + assert_line "::debug::Push commit to remote branch not-existend-remote-branch" + + run git branch + assert_line --partial "not-existend-remote-branch" + + run git branch -r + assert_line --partial "origin/not-existend-remote-branch" +} + +@test "it creates new local branch and pushes branch to remote even if the remote branch already exists" { + + # Create `existing-remote-branch` on remote with changes the local repository does not yet have + cd $FAKE_TEMP_LOCAL_REPOSITORY; + git checkout -b "existing-remote-branch" + touch new-branch-file.txt + git add new-branch-file.txt + git commit -m "Add additional file"; + git push origin existing-remote-branch; + + run git branch; + assert_line --partial "existing-remote-branch" + + # --------- + # Switch to our regular local repository and run `git-auto-commit` + cd $FAKE_LOCAL_REPOSITORY; + + INPUT_BRANCH="existing-remote-branch" + INPUT_CREATE_BRANCH=true + + run git branch + refute_line --partial "existing-remote-branch" + + run git fetch --all; + run git pull origin existing-remote-branch; + run git branch -r; + assert_line --partial "origin/existing-remote-branch" + + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-{1,2,3}.txt + + run git_auto_commit + + assert_success + + assert_line "INPUT_REPOSITORY value: ${INPUT_REPOSITORY}" + assert_line "::set-output name=changes_detected::true" + assert_line -e "::set-output name=commit_hash::[0-9a-f]{40}$" + assert_line "INPUT_BRANCH value: existing-remote-branch" + assert_line "INPUT_FILE_PATTERN: ." + assert_line "INPUT_COMMIT_OPTIONS: " + assert_line "::debug::Apply commit options " + assert_line "INPUT_TAGGING_MESSAGE: " + assert_line "No tagging message supplied. No tag will be added." + assert_line "INPUT_PUSH_OPTIONS: " + assert_line "::debug::Apply push options " + assert_line "::debug::Push commit to remote branch existing-remote-branch" + + run git branch + assert_line --partial "existing-remote-branch" + + run git branch -r + assert_line --partial "origin/existing-remote-branch" + + # Assert that branch "existing-remote-branch" was updated on remote + current_sha="$(git rev-parse --verify --short existing-remote-branch)" + remote_sha="$(git rev-parse --verify --short origin/existing-remote-branch)" + + assert_equal $current_sha $remote_sha; +} + +@test "script fails if new local branch is checked out and push fails as remote has newer commits than local" { + # Create `existing-remote-branch` on remote with changes the local repository does not yet have + cd $FAKE_TEMP_LOCAL_REPOSITORY; + git checkout -b "existing-remote-branch" + touch new-branch-file.txt + git add new-branch-file.txt + git commit -m "Add additional file"; + git push origin existing-remote-branch; + + run git branch; + assert_line --partial "existing-remote-branch" + + # --------- + # Switch to our regular local repository and run `git-auto-commit` + cd $FAKE_LOCAL_REPOSITORY; + + INPUT_BRANCH="existing-remote-branch" + INPUT_CREATE_BRANCH=true + + run git branch + refute_line --partial "existing-remote-branch" + + run git fetch --all; + run git branch -r; + assert_line --partial "origin/existing-remote-branch" + + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-{1,2,3}.txt + + run git_auto_commit + + assert_failure + + assert_line "hint: Updates were rejected because the tip of your current branch is behind" + + # Assert that branch exists locally and on remote + run git branch + assert_line --partial "existing-remote-branch" + + run git branch -r + assert_line --partial "origin/existing-remote-branch" + + # Assert that branch "existing-remote-branch" was not updated on remote + current_sha="$(git rev-parse --verify --short existing-remote-branch)" + remote_sha="$(git rev-parse --verify --short origin/existing-remote-branch)" + + refute [assert_equal $current_sha $remote_sha]; +} + +@test "It pushes commit to remote if branch already exists and local repo is behind its remote counterpart" { + # Create `new-branch` on remote with changes the local repository does not yet have + cd $FAKE_TEMP_LOCAL_REPOSITORY; + + git checkout -b "new-branch" + touch new-branch-file.txt + git add new-branch-file.txt + + git commit --quiet -m "Add additional file"; + git push origin new-branch; + + run git branch -r + assert_line --partial "origin/new-branch" + + # --------- + # Switch to our regular local repository and run `git-auto-commit` + cd $FAKE_LOCAL_REPOSITORY; + + INPUT_BRANCH="new-branch" + + # Assert that local remote does not know have "new-branch" locally nor does + # know about the remote branch. + run git branch + refute_line --partial "new-branch" + + run git branch -r + refute_line --partial "origin/new-branch" + + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-{1,2,3}.txt + + run git_auto_commit + + assert_success + + assert_line "INPUT_BRANCH value: new-branch" + assert_line --partial "::debug::Push commit to remote branch new-branch" + + # Assert that branch "new-branch" was updated on remote + current_sha="$(git rev-parse --verify --short new-branch)" + remote_sha="$(git rev-parse --verify --short origin/new-branch)" + + assert_equal $current_sha $remote_sha; +}