From 05d4d0b54f4e94ae6f92182f10646b1a27f8658d Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:14:48 +0100 Subject: [PATCH 1/9] feat: Add LBEM Edition enhancements - Add sync command to feature/bugfix (like git-town sync) - Add propose command to feature/bugfix (create PR/MR) - Add config export command to write .gitflow file - Add .gitflow file support for shared team settings - Add finish mode (classic/propose/ask) configuration - Add sync strategy (rebase/merge) configuration - Add forge detection (GitHub, GitLab, Bitbucket) - Add short flags for macOS BSD getopt compatibility - Add LBEM copyright headers --- git-flow | 3 +- git-flow-bugfix | 229 +++++++++++++++++++++++++++++++- git-flow-config | 103 +++++++++++++++ git-flow-feature | 229 +++++++++++++++++++++++++++++++- git-flow-init | 22 ++-- git-flow-release | 64 ++++----- gitflow-common | 335 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 932 insertions(+), 53 deletions(-) diff --git a/git-flow b/git-flow index dfe85e4..10c0963 100755 --- a/git-flow +++ b/git-flow @@ -10,6 +10,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -120,7 +121,7 @@ main() { . "$GITFLOW_DIR/gitflow-common" # allow user to request git action logging - DEFINE_boolean 'showcommands' false 'Show actions taken (git commands)' + DEFINE_boolean 'showcommands' false 'Show actions taken (git commands)' s # but if the user prefers that the logging is always on, # use the environmental variables. gitflow_override_flag_boolean 'showcommands' 'showcommands' diff --git a/git-flow-bugfix b/git-flow-bugfix index 297324c..0f7db7f 100644 --- a/git-flow-bugfix +++ b/git-flow-bugfix @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -59,6 +60,8 @@ git flow bugfix checkout git flow bugfix pull git flow bugfix delete git flow bugfix rename +git flow bugfix sync +git flow bugfix propose Manage your bugfix branches. @@ -261,14 +264,14 @@ no-ff! Never fast-forward during the merge DEFINE_boolean 'fetch' false "fetch from $ORIGIN before performing finish" F DEFINE_boolean 'rebase' false "rebase before merging" r DEFINE_boolean 'preserve-merges' false 'try to recreate merges while rebasing' p - DEFINE_boolean 'push' false "push to $ORIGIN after performing finish" + DEFINE_boolean 'push' false "push to $ORIGIN after performing finish" P DEFINE_boolean 'keep' false "keep branch after performing finish" k - DEFINE_boolean 'keepremote' false "keep the remote branch" - DEFINE_boolean 'keeplocal' false "keep the local branch" + DEFINE_boolean 'keepremote' false "keep the remote branch" 1 + DEFINE_boolean 'keeplocal' false "keep the local branch" 2 DEFINE_boolean 'force_delete' false "force delete bugfix branch after finish" D DEFINE_boolean 'squash' false "squash bugfix during merge" S - DEFINE_boolean 'squash-info' false "add branch info during squash" - DEFINE_boolean 'no-ff!' false "Don't fast-forward ever during merge " + DEFINE_boolean 'squash-info' false "add branch info during squash" 3 + DEFINE_boolean 'no-ff!' false "Don't fast-forward ever during merge " 4 # Override defaults with values from config gitflow_override_flag_boolean "bugfix.finish.fetch" "fetch" @@ -291,6 +294,38 @@ no-ff! Never fast-forward during the merge gitflow_use_current_branch_name fi + # LBEM Edition: Check finish mode (classic/propose/ask) + local finish_mode + finish_mode=$(gitflow_get_finish_mode "bugfix") + + case "$finish_mode" in + propose) + echo "Finish mode is 'propose'. Creating pull request instead of merging..." + cmd_propose "$@" + return $? + ;; + ask) + printf "Finish mode: [c]lassic merge, [p]ropose PR, [a]bort? " + read answer + case "$answer" in + p|P) + cmd_propose "$@" + return $? + ;; + a|A) + echo "Aborted." + exit 0 + ;; + c|C|*) + # Continue with classic finish + ;; + esac + ;; + classic|*) + # Continue with classic finish + ;; + esac + # Keeping both branches implies the --keep flag to be true. if flag keepremote && flag keeplocal; then FLAGS_keep=$FLAGS_TRUE @@ -842,3 +877,187 @@ showcommands! Show git commands while executing them " gitflow_rename_branch "$@" } + +# +# LBEM Edition: Sync command - synchronize bugfix branch with base branch +# +cmd_sync() { + OPTIONS_SPEC="\ +git flow bugfix sync [-h] [-p] [] + +Synchronize bugfix branch with its base branch (like git-town sync). +This will: + 1. Stash any uncommitted changes + 2. Fetch updates from origin + 3. Update the base branch + 4. Rebase or merge the bugfix branch on the updated base (configurable) + 5. Push the bugfix branch to origin (if -p flag or config) + 6. Restore stashed changes + +When is omitted the current branch is used, but only if it's a bugfix branch. +-- +h,help! Show this help +showcommands! Show git commands while executing them +p,[no]push Push to origin after sync +" + local did_stash sync_strategy + + # Define flags + DEFINE_boolean 'push' false "push to $ORIGIN after sync" p + + # Override defaults with values from config + gitflow_override_flag_boolean "bugfix.sync.push" "push" + + # Parse arguments + parse_args "$@" + + # Use current branch if no name is given + if [ "$NAME" = "" ]; then + gitflow_use_current_branch_name + fi + + # Sanity checks + require_branch "$BRANCH" + + BASE_BRANCH=$(gitflow_config_get_base_branch $BRANCH) + BASE_BRANCH=${BASE_BRANCH:-$DEVELOP_BRANCH} + + git_local_branch_exists "$BASE_BRANCH" || die "The base '$BASE_BRANCH' doesn't exist locally. Can't sync the bugfix branch '$BRANCH'." + + run_pre_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + # Step 1: Stash uncommitted changes + echo "Syncing bugfix branch '$BRANCH' with '$BASE_BRANCH'..." + gitflow_stash_save + did_stash=$? + + # Step 2: Fetch from origin + echo "Fetching from '$ORIGIN'..." + git_do fetch -q "$ORIGIN" || die "Could not fetch from '$ORIGIN'." + + # Step 3: Update base branch if it has a remote counterpart + if git_remote_branch_exists "$ORIGIN/$BASE_BRANCH"; then + echo "Updating base branch '$BASE_BRANCH'..." + git_do checkout -q "$BASE_BRANCH" || die "Could not check out '$BASE_BRANCH'." + git_do merge --ff-only "$ORIGIN/$BASE_BRANCH" 2>/dev/null || { + warn "Base branch '$BASE_BRANCH' has diverged from '$ORIGIN/$BASE_BRANCH'." + warn "Please resolve this manually before syncing." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Step 4: Get back to bugfix branch and sync with base + git_do checkout -q "$BRANCH" || die "Could not check out '$BRANCH'." + + # If remote bugfix branch exists, pull it first + if git_remote_branch_exists "$ORIGIN/$BRANCH"; then + echo "Pulling updates from '$ORIGIN/$BRANCH'..." + git_do pull --rebase -q "$ORIGIN" "$BRANCH" 2>/dev/null || { + warn "Could not pull from '$ORIGIN/$BRANCH'. You may need to resolve conflicts." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Rebase or merge based on strategy + sync_strategy=$(gitflow_get_sync_strategy "bugfix") + echo "Syncing with base branch using $sync_strategy strategy..." + + if [ "$sync_strategy" = "rebase" ]; then + git_do rebase "$BASE_BRANCH" || { + warn "Rebase failed. Please resolve conflicts and run 'git rebase --continue'." + warn "After resolving, run 'git flow bugfix sync' again." + gitflow_stash_pop $did_stash + exit 1 + } + else + git_do merge --no-edit "$BASE_BRANCH" || { + warn "Merge failed. Please resolve conflicts and commit." + warn "After resolving, run 'git flow bugfix sync' again." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Step 5: Push if requested + if flag push; then + echo "Pushing '$BRANCH' to '$ORIGIN'..." + if [ "$sync_strategy" = "rebase" ]; then + git_do push --force-with-lease "$ORIGIN" "$BRANCH" || warn "Could not push to '$ORIGIN'. You may need to push manually." + else + git_do push "$ORIGIN" "$BRANCH" || warn "Could not push to '$ORIGIN'. You may need to push manually." + fi + fi + + # Step 6: Restore stashed changes + gitflow_stash_pop $did_stash + + run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + echo + echo "Summary of actions:" + echo "- Bugfix branch '$BRANCH' was synced with '$BASE_BRANCH'" + if flag push; then + echo "- Bugfix branch '$BRANCH' was pushed to '$ORIGIN'" + fi + echo "- You are now on branch '$(git_current_branch)'" + echo +} + +# +# LBEM Edition: Propose command - create a pull/merge request +# +cmd_propose() { + OPTIONS_SPEC="\ +git flow bugfix propose [-h] [] + +Create a pull request (or merge request) for bugfix branch . +This will: + 1. Push the bugfix branch to origin (if not already pushed) + 2. Create a PR using gh/glab CLI (if available) or open browser + +When is omitted the current branch is used, but only if it's a bugfix branch. +-- +h,help! Show this help +showcommands! Show git commands while executing them +" + # Parse arguments + parse_args "$@" + + # Use current branch if no name is given + if [ "$NAME" = "" ]; then + gitflow_use_current_branch_name + fi + + # Sanity checks + require_clean_working_tree + require_branch "$BRANCH" + + BASE_BRANCH=$(gitflow_config_get_base_branch $BRANCH) + BASE_BRANCH=${BASE_BRANCH:-$DEVELOP_BRANCH} + + run_pre_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + # Push branch to origin if not already there + if ! git_remote_branch_exists "$ORIGIN/$BRANCH"; then + echo "Publishing '$BRANCH' to '$ORIGIN'..." + git_do push -u "$ORIGIN" "$BRANCH:$BRANCH" || die "Could not push '$BRANCH' to '$ORIGIN'." + else + # Make sure local branch is up to date with remote + echo "Pushing any local changes to '$ORIGIN/$BRANCH'..." + git_do push "$ORIGIN" "$BRANCH" || die "Could not push '$BRANCH' to '$ORIGIN'." + fi + + # Create PR + gitflow_create_pr "$BRANCH" "$BASE_BRANCH" "bugfix" + + run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + echo + echo "Summary of actions:" + echo "- Bugfix branch '$BRANCH' was pushed to '$ORIGIN'" + echo "- A pull request was created (or browser opened) targeting '$BASE_BRANCH'" + echo "- You are now on branch '$(git_current_branch)'" + echo +} \ No newline at end of file diff --git a/git-flow-config b/git-flow-config index 70843c9..b9e6223 100644 --- a/git-flow-config +++ b/git-flow-config @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -48,6 +49,7 @@ usage() { git flow config [list] git flow config set git flow config base +git flow config export Manage the git-flow configuration. @@ -131,6 +133,16 @@ file= Use given config file output=$(git config $gitflow_config_option --get gitflow.prefix.versiontag) echo "Version tag prefix: $output " + + # LBEM Edition: Additional config options + output=$(git config $gitflow_config_option --get gitflow.finish.mode) + echo "Finish mode (classic/propose/ask): ${output:-classic} " + + output=$(git config $gitflow_config_option --get gitflow.sync.strategy) + echo "Sync strategy (rebase/merge): ${output:-rebase} " + + output=$(git config $gitflow_config_option --get gitflow.propose.open) + echo "Auto-open browser on propose: ${output:-true} " } cmd_set() { @@ -195,6 +207,18 @@ file= Use given config file cfg_option="gitflow.multi-hotfix" txt="Allow multiple hotfix branches" ;; + finishmode) + cfg_option="gitflow.finish.mode" + txt="Default finish mode (classic/propose/ask)" + ;; + syncstrategy) + cfg_option="gitflow.sync.strategy" + txt="Sync strategy (rebase/merge)" + ;; + proposeopen) + cfg_option="gitflow.propose.open" + txt="Auto-open browser on propose" + ;; *) die_help "Invalid option given." ;; @@ -239,6 +263,30 @@ file= Use given config file esac fi + # LBEM Edition: Validate finish mode + if [ $OPTION = "finishmode" ]; then + gitflow_check_finish_mode "${value}" || \ + die "Invalid value for option 'finishmode'. Valid values are 'classic', 'propose', or 'ask'" + fi + + # LBEM Edition: Validate sync strategy + if [ $OPTION = "syncstrategy" ]; then + gitflow_check_sync_strategy "${value}" || \ + die "Invalid value for option 'syncstrategy'. Valid values are 'rebase' or 'merge'" + fi + + # LBEM Edition: Validate propose open + if [ $OPTION = "proposeopen" ]; then + check_boolean "${value}" + case $? in + ${FLAGS_ERROR}) + die "Invalid value for option 'proposeopen'. Valid values are 'true' or 'false'" + ;; + *) + ;; + esac + fi + git_do config $gitflow_config_option $cfg_option "$value" case $? in @@ -304,6 +352,61 @@ cmd_help() { exit 0 } +# +# LBEM Edition: Export command - write current config to .gitflow file +# +cmd_export() { + OPTIONS_SPEC="\ +git flow config export + +Export the current git-flow configuration to a .gitflow file in the repository root. +This file can be committed to share settings with your team. +-- +h,help! Show this help +" + local dotgitflow_file + + FLAGS "$@" || exit $? + + dotgitflow_file="$GIT_CURRENT_REPO_DIR/.gitflow" + + echo "Exporting git-flow configuration to '$dotgitflow_file'..." + + # Create or overwrite the .gitflow file + cat > "$dotgitflow_file" << EOF +# git-flow configuration file +# This file can be committed to share git-flow settings with your team. +# Generated by git-flow LBEM Edition + +[branch] + master = $(git config --get gitflow.branch.master) + develop = $(git config --get gitflow.branch.develop) + +[prefix] + feature = $(git config --get gitflow.prefix.feature) + bugfix = $(git config --get gitflow.prefix.bugfix) + release = $(git config --get gitflow.prefix.release) + hotfix = $(git config --get gitflow.prefix.hotfix) + support = $(git config --get gitflow.prefix.support) + versiontag = $(git config --get gitflow.prefix.versiontag) + +[finish] + mode = $(git config --get gitflow.finish.mode || echo classic) + +[sync] + strategy = $(git config --get gitflow.sync.strategy || echo rebase) + +[propose] + open = $(git config --get gitflow.propose.open || echo true) +EOF + + echo + echo "Summary of actions:" + echo "- Configuration exported to '$dotgitflow_file'" + echo "- You can commit this file to share settings with your team" + echo +} + # Private functions __set_base () { diff --git a/git-flow-feature b/git-flow-feature index c5c5eea..17863ba 100644 --- a/git-flow-feature +++ b/git-flow-feature @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -60,6 +61,8 @@ git flow feature pull git flow feature delete git flow feature rename git flow feature release +git flow feature sync +git flow feature propose Manage your feature branches. @@ -262,14 +265,14 @@ no-ff! Never fast-forward during the merge DEFINE_boolean 'fetch' false "fetch from $ORIGIN before performing finish" F DEFINE_boolean 'rebase' false "rebase before merging" r DEFINE_boolean 'preserve-merges' false 'try to recreate merges while rebasing' p - DEFINE_boolean 'push' false "push to $ORIGIN after performing finish" + DEFINE_boolean 'push' false "push to $ORIGIN after performing finish" P DEFINE_boolean 'keep' false "keep branch after performing finish" k - DEFINE_boolean 'keepremote' false "keep the remote branch" - DEFINE_boolean 'keeplocal' false "keep the local branch" + DEFINE_boolean 'keepremote' false "keep the remote branch" 1 + DEFINE_boolean 'keeplocal' false "keep the local branch" 2 DEFINE_boolean 'force_delete' false "force delete feature branch after finish" D DEFINE_boolean 'squash' false "squash feature during merge" S - DEFINE_boolean 'squash-info' false "add branch info during squash" - DEFINE_boolean 'no-ff!' false "Don't fast-forward ever during merge " + DEFINE_boolean 'squash-info' false "add branch info during squash" 3 + DEFINE_boolean 'no-ff!' false "Don't fast-forward ever during merge " 4 # Override defaults with values from config gitflow_override_flag_boolean "feature.finish.fetch" "fetch" @@ -292,6 +295,38 @@ no-ff! Never fast-forward during the merge gitflow_use_current_branch_name fi + # LBEM Edition: Check finish mode (classic/propose/ask) + local finish_mode + finish_mode=$(gitflow_get_finish_mode "feature") + + case "$finish_mode" in + propose) + echo "Finish mode is 'propose'. Creating pull request instead of merging..." + cmd_propose "$@" + return $? + ;; + ask) + printf "Finish mode: [c]lassic merge, [p]ropose PR, [a]bort? " + read answer + case "$answer" in + p|P) + cmd_propose "$@" + return $? + ;; + a|A) + echo "Aborted." + exit 0 + ;; + c|C|*) + # Continue with classic finish + ;; + esac + ;; + classic|*) + # Continue with classic finish + ;; + esac + # Keeping both branches implies the --keep flag to be true. if flag keepremote && flag keeplocal; then FLAGS_keep=$FLAGS_TRUE @@ -874,3 +909,187 @@ showcommands! Show git commands while executing them " gitflow_rename_branch "$@" } + +# +# LBEM Edition: Sync command - synchronize feature branch with base branch +# +cmd_sync() { + OPTIONS_SPEC="\ +git flow feature sync [-h] [-p] [] + +Synchronize feature branch with its base branch (like git-town sync). +This will: + 1. Stash any uncommitted changes + 2. Fetch updates from origin + 3. Update the base branch + 4. Rebase or merge the feature branch on the updated base (configurable) + 5. Push the feature branch to origin (if -p flag or config) + 6. Restore stashed changes + +When is omitted the current branch is used, but only if it's a feature branch. +-- +h,help! Show this help +showcommands! Show git commands while executing them +p,[no]push Push to origin after sync +" + local did_stash sync_strategy + + # Define flags + DEFINE_boolean 'push' false "push to $ORIGIN after sync" p + + # Override defaults with values from config + gitflow_override_flag_boolean "feature.sync.push" "push" + + # Parse arguments + parse_args "$@" + + # Use current branch if no name is given + if [ "$NAME" = "" ]; then + gitflow_use_current_branch_name + fi + + # Sanity checks + require_branch "$BRANCH" + + BASE_BRANCH=$(gitflow_config_get_base_branch $BRANCH) + BASE_BRANCH=${BASE_BRANCH:-$DEVELOP_BRANCH} + + git_local_branch_exists "$BASE_BRANCH" || die "The base '$BASE_BRANCH' doesn't exist locally. Can't sync the feature branch '$BRANCH'." + + run_pre_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + # Step 1: Stash uncommitted changes + echo "Syncing feature branch '$BRANCH' with '$BASE_BRANCH'..." + gitflow_stash_save + did_stash=$? + + # Step 2: Fetch from origin + echo "Fetching from '$ORIGIN'..." + git_do fetch -q "$ORIGIN" || die "Could not fetch from '$ORIGIN'." + + # Step 3: Update base branch if it has a remote counterpart + if git_remote_branch_exists "$ORIGIN/$BASE_BRANCH"; then + echo "Updating base branch '$BASE_BRANCH'..." + git_do checkout -q "$BASE_BRANCH" || die "Could not check out '$BASE_BRANCH'." + git_do merge --ff-only "$ORIGIN/$BASE_BRANCH" 2>/dev/null || { + warn "Base branch '$BASE_BRANCH' has diverged from '$ORIGIN/$BASE_BRANCH'." + warn "Please resolve this manually before syncing." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Step 4: Get back to feature branch and sync with base + git_do checkout -q "$BRANCH" || die "Could not check out '$BRANCH'." + + # If remote feature branch exists, pull it first + if git_remote_branch_exists "$ORIGIN/$BRANCH"; then + echo "Pulling updates from '$ORIGIN/$BRANCH'..." + git_do pull --rebase -q "$ORIGIN" "$BRANCH" 2>/dev/null || { + warn "Could not pull from '$ORIGIN/$BRANCH'. You may need to resolve conflicts." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Rebase or merge based on strategy + sync_strategy=$(gitflow_get_sync_strategy "feature") + echo "Syncing with base branch using $sync_strategy strategy..." + + if [ "$sync_strategy" = "rebase" ]; then + git_do rebase "$BASE_BRANCH" || { + warn "Rebase failed. Please resolve conflicts and run 'git rebase --continue'." + warn "After resolving, run 'git flow feature sync' again." + gitflow_stash_pop $did_stash + exit 1 + } + else + git_do merge --no-edit "$BASE_BRANCH" || { + warn "Merge failed. Please resolve conflicts and commit." + warn "After resolving, run 'git flow feature sync' again." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Step 5: Push if requested + if flag push; then + echo "Pushing '$BRANCH' to '$ORIGIN'..." + if [ "$sync_strategy" = "rebase" ]; then + git_do push --force-with-lease "$ORIGIN" "$BRANCH" || warn "Could not push to '$ORIGIN'. You may need to push manually." + else + git_do push "$ORIGIN" "$BRANCH" || warn "Could not push to '$ORIGIN'. You may need to push manually." + fi + fi + + # Step 6: Restore stashed changes + gitflow_stash_pop $did_stash + + run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + echo + echo "Summary of actions:" + echo "- Feature branch '$BRANCH' was synced with '$BASE_BRANCH'" + if flag push; then + echo "- Feature branch '$BRANCH' was pushed to '$ORIGIN'" + fi + echo "- You are now on branch '$(git_current_branch)'" + echo +} + +# +# LBEM Edition: Propose command - create a pull/merge request +# +cmd_propose() { + OPTIONS_SPEC="\ +git flow feature propose [-h] [] + +Create a pull request (or merge request) for feature branch . +This will: + 1. Push the feature branch to origin (if not already pushed) + 2. Create a PR using gh/glab CLI (if available) or open browser + +When is omitted the current branch is used, but only if it's a feature branch. +-- +h,help! Show this help +showcommands! Show git commands while executing them +" + # Parse arguments + parse_args "$@" + + # Use current branch if no name is given + if [ "$NAME" = "" ]; then + gitflow_use_current_branch_name + fi + + # Sanity checks + require_clean_working_tree + require_branch "$BRANCH" + + BASE_BRANCH=$(gitflow_config_get_base_branch $BRANCH) + BASE_BRANCH=${BASE_BRANCH:-$DEVELOP_BRANCH} + + run_pre_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + # Push branch to origin if not already there + if ! git_remote_branch_exists "$ORIGIN/$BRANCH"; then + echo "Publishing '$BRANCH' to '$ORIGIN'..." + git_do push -u "$ORIGIN" "$BRANCH:$BRANCH" || die "Could not push '$BRANCH' to '$ORIGIN'." + else + # Make sure local branch is up to date with remote + echo "Pushing any local changes to '$ORIGIN/$BRANCH'..." + git_do push "$ORIGIN" "$BRANCH" || die "Could not push '$BRANCH' to '$ORIGIN'." + fi + + # Create PR + gitflow_create_pr "$BRANCH" "$BASE_BRANCH" "feature" + + run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" + + echo + echo "Summary of actions:" + echo "- Feature branch '$BRANCH' was pushed to '$ORIGIN'" + echo "- A pull request was created (or browser opened) targeting '$BASE_BRANCH'" + echo "- You are now on branch '$(git_current_branch)'" + echo +} \ No newline at end of file diff --git a/git-flow-init b/git-flow-init index 8a4d767..a66be26 100644 --- a/git-flow-init +++ b/git-flow-init @@ -64,7 +64,7 @@ git flow init [-h] [-d] [-f] [-g] Setup a git repository for git flow usage. Can also be used to start a git repository. -- h,help! Show this help -showcommands! Show git commands while executing them +v,showcommands! Show git commands while executing them d,[no]defaults Use default branch naming conventions f,[no]force Force setting of gitflow branches, even if already configured g,[no]sign Sign initial commit when creating a repository @@ -73,14 +73,14 @@ p,feature! Feature branches b,bugfix! Bugfix branches r,release! Release branches x,hotfix! Hotfix branches -s,support! Support branches +u,support! Support branches t,tag! Version tag prefix Use config file location -local! use repository config file -global! use global config file -system! use system config file -file= use given config file +l,local! use repository config file +o,global! use global config file +y,system! use system config file +c:,file= use given config file " local gitflow_config_option should_check_existence branchcount guess local master_branch develop_branch default_suggestion answer prefix @@ -88,16 +88,16 @@ file= use given config file # Define flags DEFINE_boolean 'force' false 'force setting of gitflow branches, even if already configured' f DEFINE_boolean 'defaults' false 'use default branch naming conventions' d - DEFINE_boolean 'local' false 'use repository config file' - DEFINE_boolean 'global' false 'use global config file' - DEFINE_boolean 'system' false 'use system config file' + DEFINE_boolean 'local' false 'use repository config file' l + DEFINE_boolean 'global' false 'use global config file' o + DEFINE_boolean 'system' false 'use system config file' y DEFINE_boolean 'sign' false 'sign initial commit when creating a repository' g - DEFINE_string 'file' "" 'use given config file' + DEFINE_string 'file' "" 'use given config file' c DEFINE_string 'feature' "" 'feature branches' p DEFINE_string 'bugfix' "" 'bugfix branches' b DEFINE_string 'release' "" 'release branches' r DEFINE_string 'hotfix' "" 'hotfix branches' x - DEFINE_string 'support' "" 'support branches' s + DEFINE_string 'support' "" 'support branches' u DEFINE_string 'tag' "" 'version tag prefix' t # Override defaults with values from config diff --git a/git-flow-release b/git-flow-release index 3acdfd3..5ca7ead 100644 --- a/git-flow-release +++ b/git-flow-release @@ -564,20 +564,21 @@ u,signingkey! Use the given GPG-key for the digital signature (implies -s) m,message! Use the given tag message f,[no]messagefile= Use the contents of the given file as a tag message p,[no]push Push to origin after performing finish -[no]pushproduction Push the production branch -[no]pushdevelop Push the develop branch -[no]pushtag Push the tag +1,[no]pushproduction Push the production branch +2,[no]pushdevelop Push the develop branch +3,[no]pushtag Push the tag k,[no]keep Keep branch after performing finish -[no]keepremote Keep the remote branch -[no]keeplocal Keep the local branch +4,[no]keepremote Keep the remote branch +5,[no]keeplocal Keep the local branch D,[no]force_delete Force delete release branch after finish n,[no]tag Don't tag this release b,[no]nobackmerge Don't back-merge master, or tag if applicable, in develop S,[no]squash Squash release during merge -[no]ff-master Fast forward master branch if possible +6,[no]squash-info Add branch info during squash +7,[no]ff-master Fast forward master branch if possible e,[no]edit The --noedit option can be used to accept the auto-generated message on merging T,tagname! Use given tag name -nodevelopmerge! Don't back-merge develop branch +8,nodevelopmerge! Don't back-merge develop branch " local base @@ -589,21 +590,21 @@ nodevelopmerge! Don't back-merge develop branch DEFINE_string 'message' "" "use the given tag message" m DEFINE_string 'messagefile' "" "use the contents of the given file as a tag message" f DEFINE_boolean 'push' false "push to $ORIGIN after performing finish" p - DEFINE_boolean 'pushproduction' false "push the production branch" - DEFINE_boolean 'pushdevelop' false "push the develop branch" - DEFINE_boolean 'pushtag' false "push the tag" + DEFINE_boolean 'pushproduction' false "push the production branch" 1 + DEFINE_boolean 'pushdevelop' false "push the develop branch" 2 + DEFINE_boolean 'pushtag' false "push the tag" 3 DEFINE_boolean 'keep' false "keep branch after performing finish" k - DEFINE_boolean 'keepremote' false "keep the remote branch" - DEFINE_boolean 'keeplocal' false "keep the local branch" + DEFINE_boolean 'keepremote' false "keep the remote branch" 4 + DEFINE_boolean 'keeplocal' false "keep the local branch" 5 DEFINE_boolean 'force_delete' false "force delete release branch after finish" D DEFINE_boolean 'notag' false "don't tag this release" n DEFINE_boolean 'nobackmerge' false "don't back-merge $MASTER_BRANCH, or tag if applicable, in $DEVELOP_BRANCH " b DEFINE_boolean 'squash' false "squash release during merge" S - DEFINE_boolean 'squash-info' false "add branch info during squash" - DEFINE_boolean 'ff-master' false "fast forward master branch if possible" + DEFINE_boolean 'squash-info' false "add branch info during squash" 6 + DEFINE_boolean 'ff-master' false "fast forward master branch if possible" 7 DEFINE_boolean 'edit' true "accept the auto-generated message on merging" e DEFINE_string 'tagname' "" "use the given tag name" T - DEFINE_boolean 'nodevelopmerge' false "don't merge $BRANCH into $DEVELOP_BRANCH " + DEFINE_boolean 'nodevelopmerge' false "don't merge $BRANCH into $DEVELOP_BRANCH " 8 # Override defaults with values from config gitflow_override_flag_boolean "release.finish.fetch" "fetch" @@ -817,27 +818,28 @@ git flow release finish [-h] [-F] [-s] [-u] [-m | -f] [-p] [-k] [-n] [-b] [-S] [ Finish a release branch -- h,help Show this help -showcommands! Show git commands while executing them +v,showcommands! Show git commands while executing them F,[no]fetch Fetch from origin before performing finish s,sign! Sign the release tag cryptographically u,signingkey! Use the given GPG-key for the digital signature (implies -s) m,message! Use the given tag message f,[no]messagefile= Use the contents of the given file as a tag message p,[no]push Push to origin after performing finish -[no]pushproduction Push the production branch -[no]pushdevelop Push the develop branch -[no]pushtag Push the tag +1,[no]pushproduction Push the production branch +2,[no]pushdevelop Push the develop branch +3,[no]pushtag Push the tag k,[no]keep Keep branch after performing finish -[no]keepremote Keep the remote branch -[no]keeplocal Keep the local branch +4,[no]keepremote Keep the remote branch +5,[no]keeplocal Keep the local branch D,[no]force_delete Force delete release branch after finish n,[no]tag Don't tag this release b,[no]nobackmerge Don't back-merge master, or tag if applicable, in develop S,[no]squash Squash release during merge -[no]ff-master Fast forward master branch if possible +6,[no]squash-info Add branch info during squash +7,[no]ff-master Fast forward master branch if possible e,[no]edit The --noedit option can be used to accept the auto-generated message on merging T,tagname! Use given tag name -nodevelopmerge! Don't back-merge develop branch +8,nodevelopmerge! Don't back-merge develop branch " # Define flags DEFINE_boolean 'fetch' false "fetch from $ORIGIN before performing finish" F @@ -846,21 +848,21 @@ nodevelopmerge! Don't back-merge develop branch DEFINE_string 'message' "" "use the given tag message" m DEFINE_string 'messagefile' "" "use the contents of the given file as a tag message" f DEFINE_boolean 'push' false "push to $ORIGIN after performing finish" p - DEFINE_boolean 'pushproduction' false "push the production branch" - DEFINE_boolean 'pushdevelop' false "push the develop branch" - DEFINE_boolean 'pushtag' false "push the tag" + DEFINE_boolean 'pushproduction' false "push the production branch" 1 + DEFINE_boolean 'pushdevelop' false "push the develop branch" 2 + DEFINE_boolean 'pushtag' false "push the tag" 3 DEFINE_boolean 'keep' false "keep branch after performing finish" k - DEFINE_boolean 'keepremote' false "keep the remote branch" - DEFINE_boolean 'keeplocal' false "keep the local branch" + DEFINE_boolean 'keepremote' false "keep the remote branch" 4 + DEFINE_boolean 'keeplocal' false "keep the local branch" 5 DEFINE_boolean 'force_delete' false "force delete release branch after finish" D DEFINE_boolean 'notag' false "don't tag this release" n DEFINE_boolean 'nobackmerge' false "don't back-merge $MASTER_BRANCH, or tag if applicable, in $DEVELOP_BRANCH " b DEFINE_boolean 'squash' false "squash release during merge" S - DEFINE_boolean 'squash-info' false "add branch info during squash" - DEFINE_boolean 'ff-master' false "fast forward master branch if possible" + DEFINE_boolean 'squash-info' false "add branch info during squash" 6 + DEFINE_boolean 'ff-master' false "fast forward master branch if possible" 7 DEFINE_boolean 'edit' true "accept the auto-generated message on merging" e DEFINE_string 'tagname' "" "use the given tag name" T - DEFINE_boolean 'nodevelopmerge' false "don't merge $BRANCH into $DEVELOP_BRANCH " + DEFINE_boolean 'nodevelopmerge' false "don't merge $BRANCH into $DEVELOP_BRANCH " 8 # Override defaults with values from config gitflow_override_flag_boolean "release.finish.fetch" "fetch" diff --git a/gitflow-common b/gitflow-common index 8dd0b9f..1e4178e 100644 --- a/gitflow-common +++ b/gitflow-common @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -319,6 +320,9 @@ gitflow_load_settings() { done; mv "$GITFLOW_CONFIG" "$GITFLOW_CONFIG".backup 2>/dev/null fi + + # LBEM Edition: Load settings from .gitflow file in repo root + gitflow_load_dotgitflow } # @@ -498,6 +502,337 @@ gitflow_override_flag_string() { return ${FLAGS_TRUE} } +# +# LBEM Edition: Additional functionality for sync, propose, and finish mode +# + +# gitflow_check_finish_mode() +# +# Validate finish mode config value +# +# Param $1: string Value to validate +# Return: 0=valid, 1=invalid +# +gitflow_check_finish_mode() { + local _value + _value="${1}" + case "${_value}" in + classic|propose|ask) + return 0 + ;; + *) + return 1 + ;; + esac +} + +# gitflow_get_finish_mode() +# +# Get the finish mode for a subcommand (feature, bugfix, etc.) +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string "classic"|"propose"|"ask" (defaults to "classic") +# +gitflow_get_finish_mode() { + local _mode _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _mode=$(git config --get gitflow.${_subcommand}.finish.mode 2>/dev/null) + [ -z "$_mode" ] && _mode=$(git config --get gitflow.finish.mode 2>/dev/null) + [ -z "$_mode" ] && _mode="classic" + + echo "$_mode" +} + +# gitflow_check_sync_strategy() +# +# Validate sync strategy config value +# +# Param $1: string Value to validate +# Return: 0=valid, 1=invalid +# +gitflow_check_sync_strategy() { + local _value + _value="${1}" + case "${_value}" in + rebase|merge) + return 0 + ;; + *) + return 1 + ;; + esac +} + +# gitflow_get_sync_strategy() +# +# Get the sync strategy for a subcommand (feature, bugfix, etc.) +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string "rebase"|"merge" (defaults to "rebase") +# +gitflow_get_sync_strategy() { + local _strategy _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _strategy=$(git config --get gitflow.${_subcommand}.sync.strategy 2>/dev/null) + [ -z "$_strategy" ] && _strategy=$(git config --get gitflow.sync.strategy 2>/dev/null) + [ -z "$_strategy" ] && _strategy="rebase" + + echo "$_strategy" +} + +# gitflow_get_propose_open() +# +# Check if browser should auto-open after propose +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string "true"|"false" (defaults to "true") +# +gitflow_get_propose_open() { + local _open _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _open=$(git config --bool --get gitflow.${_subcommand}.propose.open 2>/dev/null) + [ -z "$_open" ] && _open=$(git config --bool --get gitflow.propose.open 2>/dev/null) + [ -z "$_open" ] && _open="true" + + echo "$_open" +} + +# gitflow_detect_forge() +# +# Detect the forge type (github, gitlab, bitbucket) from remote URL +# +# Return: string "github"|"gitlab"|"bitbucket"|"unknown" +# +gitflow_detect_forge() { + local _remote_url + + _remote_url=$(git config --get remote.${ORIGIN}.url 2>/dev/null) + + case "$_remote_url" in + *github.com*|*github.*) + echo "github" + ;; + *gitlab.com*|*gitlab.*) + echo "gitlab" + ;; + *bitbucket.org*|*bitbucket.*) + echo "bitbucket" + ;; + *) + echo "unknown" + ;; + esac +} + +# gitflow_get_repo_url() +# +# Get the web URL of the repository +# +# Return: string The HTTPS URL of the repository +# +gitflow_get_repo_url() { + local _remote_url _web_url + + _remote_url=$(git config --get remote.${ORIGIN}.url 2>/dev/null) + + # Convert SSH URL to HTTPS URL + # git@github.com:user/repo.git -> https://github.com/user/repo + # https://github.com/user/repo.git -> https://github.com/user/repo + _web_url=$(echo "$_remote_url" | sed -e 's/^git@/https:\/\//' -e 's/\.git$//' -e 's/:/\//' -e 's/https\/\//https:\/\//') + + echo "$_web_url" +} + +# gitflow_open_url() +# +# Open a URL in the default browser (cross-platform) +# +# Param $1: string URL to open +# +gitflow_open_url() { + local _url + _url="$1" + + case "$(uname -s)" in + Darwin) + open "$_url" + ;; + Linux) + if command -v xdg-open >/dev/null 2>&1; then + xdg-open "$_url" + elif command -v gnome-open >/dev/null 2>&1; then + gnome-open "$_url" + else + warn "Could not detect the web browser to use." + fi + ;; + *MINGW*|*CYGWIN*|*MSYS*) + start "$_url" + ;; + *) + warn "Could not detect the web browser to use." + ;; + esac +} + +# gitflow_has_cli_tool() +# +# Check if a CLI tool (gh, glab) is available and authenticated +# +# Param $1: string Tool name ("gh", "glab") +# Return: 0=available, 1=not available +# +gitflow_has_cli_tool() { + local _tool + _tool="$1" + + case "$_tool" in + gh) + command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 + ;; + glab) + command -v glab >/dev/null 2>&1 && glab auth status >/dev/null 2>&1 + ;; + *) + return 1 + ;; + esac +} + +# gitflow_stash_save() +# +# Stash any uncommitted changes (like git-town sync) +# +# Return: 0=stashed, 1=nothing to stash +# +gitflow_stash_save() { + local _status + + # Check if there are uncommitted changes + git_is_clean_working_tree + _status=$? + + if [ $_status -ne 0 ]; then + git_do stash push -m "gitflow: auto-stash before sync" || die "Could not stash changes" + return 0 + fi + return 1 +} + +# gitflow_stash_pop() +# +# Restore stashed changes if we stashed earlier +# +# Param $1: boolean Whether we stashed (0=yes, 1=no) +# +gitflow_stash_pop() { + local _did_stash + _did_stash="$1" + + if [ "$_did_stash" -eq 0 ]; then + git_do stash pop || warn "Could not restore stashed changes. Run 'git stash pop' manually." + fi +} + +# gitflow_load_dotgitflow() +# +# Load settings from .gitflow file in repo root if it exists +# This is called from gitflow_load_settings +# +gitflow_load_dotgitflow() { + local _dotgitflow_file _config_lines _config_line _key _value + + _dotgitflow_file="$GIT_CURRENT_REPO_DIR/.gitflow" + + if [ -f "$_dotgitflow_file" ]; then + _config_lines=$(git config --list --file="$_dotgitflow_file" 2>/dev/null) + for _config_line in ${_config_lines}; do + _key=${_config_line%%=*} + _value=${_config_line#*=} + # Only set if not already set in local config (local config takes precedence) + if ! git config --local --get "gitflow.${_key}" >/dev/null 2>&1; then + git_do config --local "gitflow.${_key}" "${_value}" 2>/dev/null + fi + done + fi +} + +# gitflow_create_pr() +# +# Create a pull request using CLI tool or fallback to URL +# +# Param $1: string Branch name +# Param $2: string Base branch +# Param $3: string Subcommand (feature, bugfix) for config +# +gitflow_create_pr() { + local _branch _base _subcommand _forge _repo_url _pr_url _open_browser _title + + _branch="$1" + _base="$2" + _subcommand="$3" + _forge=$(gitflow_detect_forge) + _repo_url=$(gitflow_get_repo_url) + _open_browser=$(gitflow_get_propose_open "$_subcommand") + _title="${_branch#*/}" # Remove prefix for title + + case "$_forge" in + github) + if gitflow_has_cli_tool "gh"; then + echo "Creating pull request using GitHub CLI..." + gh pr create --base "$_base" --head "$_branch" --title "$_title" --fill + if [ "$_open_browser" = "true" ]; then + gh pr view --web + fi + else + _pr_url="${_repo_url}/compare/${_base}...${_branch}?expand=1" + echo "GitHub CLI not available. Opening browser to create PR..." + echo "URL: $_pr_url" + if [ "$_open_browser" = "true" ]; then + gitflow_open_url "$_pr_url" + fi + fi + ;; + gitlab) + if gitflow_has_cli_tool "glab"; then + echo "Creating merge request using GitLab CLI..." + glab mr create --source-branch "$_branch" --target-branch "$_base" --title "$_title" --fill + if [ "$_open_browser" = "true" ]; then + glab mr view --web + fi + else + _pr_url="${_repo_url}/-/merge_requests/new?merge_request%5Bsource_branch%5D=${_branch}&merge_request%5Btarget_branch%5D=${_base}" + echo "GitLab CLI not available. Opening browser to create MR..." + echo "URL: $_pr_url" + if [ "$_open_browser" = "true" ]; then + gitflow_open_url "$_pr_url" + fi + fi + ;; + bitbucket) + _pr_url="${_repo_url}/pull-requests/new?source=${_branch}&dest=${_base}" + echo "Opening browser to create PR on Bitbucket..." + echo "URL: $_pr_url" + if [ "$_open_browser" = "true" ]; then + gitflow_open_url "$_pr_url" + fi + ;; + *) + warn "Unknown forge. Cannot create PR automatically." + warn "Please create a PR manually for branch '$_branch' targeting '$_base'." + return 1 + ;; + esac + + return 0 +} + # gitflow_create_squash_message() # # Create the squash message, overriding the one generated by git itself From 17497b8612ddd554a767de32f7884ca36427a0c4 Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:15:10 +0100 Subject: [PATCH 2/9] ci: Add workflow to trigger Homebrew formula update on tag push Triggers repository_dispatch to homebrew-gitflow-lbem repo when a version tag (v*) is pushed, enabling automatic formula updates. --- .github/workflows/trigger-homebrew.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/trigger-homebrew.yml diff --git a/.github/workflows/trigger-homebrew.yml b/.github/workflows/trigger-homebrew.yml new file mode 100644 index 0000000..abf3530 --- /dev/null +++ b/.github/workflows/trigger-homebrew.yml @@ -0,0 +1,19 @@ +name: Trigger Homebrew Formula Update + +on: + push: + tags: + - 'v*' + +jobs: + trigger: + runs-on: ubuntu-latest + steps: + - name: Trigger Homebrew formula update + run: | + curl -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/weindel/homebrew-gitflow-lbem/dispatches \ + -d '{"event_type":"new-release","client_payload":{"tag":"${{ github.ref_name }}"}}' From 8eb94021029a5556ca414cdd9b60ae42c3569f76 Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:27:58 +0100 Subject: [PATCH 3/9] pump version --- git-flow-version | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-flow-version b/git-flow-version index e6647ee..ceb67af 100644 --- a/git-flow-version +++ b/git-flow-version @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -39,7 +40,7 @@ # -GITFLOW_VERSION=2.2.1 +GITFLOW_VERSION=2.2.1-lbem.1 initialize() { # A function can not be empty. Comments count as empty. @@ -59,7 +60,7 @@ For more specific help type the command followed by --help } cmd_default() { - echo "$GITFLOW_VERSION (CJS Edition)" + echo "$GITFLOW_VERSION (LBEM Edition, based on CJS/AVH)" } cmd_help() { From f3dab6b794b08901c749e5d741702603955729eb Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:43:20 +0100 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9C=A8=20feat:=20Update=20README=20for?= =?UTF-8?q?=20LBEM=20Edition=20features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 131 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 110 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 637867c..a988619 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,45 @@ -# git-flow (CJS Edition) +# git-flow (LBEM Edition) A collection of Git extensions to provide high-level repository operations for Vincent Driessen's [branching model](http://nvie.com/git-model "original -blog post"). This fork adds functionality not added to the original branch. +blog post"). + +## About LBEM Edition + +This is a fork of [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs) that adds workflow enhancements inspired by [git-town](https://www.git-town.com/). LBEM Edition stays close to the CJS version to easily pull upstream bugfixes while adding the following features: + +### Additional Commands + +- **`git flow feature sync`** / **`git flow bugfix sync`** - Sync your branch with the base branch (like `git-town sync`). Handles uncommitted changes automatically, supports rebase or merge strategy. +- **`git flow feature propose`** / **`git flow bugfix propose`** - Create a pull request for your branch using `gh` CLI (falls back to opening browser URL). +- **`git flow config export`** - Export gitflow settings to a `.gitflow` file for team sharing. + +### Configuration Enhancements + +- **`.gitflow` file support** - Store gitflow configuration in a shareable file. When a developer clones a repo with a `.gitflow` file and runs `git flow init`, settings are automatically imported. +- **Finish mode** - Configure how `finish` behaves: `classic` (traditional merge), `propose` (create PR), or `ask` (prompt each time). Set via `git flow config set finishmode `. +- **Sync strategy** - Choose `rebase` (default) or `merge` for sync operations. Set via `git flow config set syncstrategy `. + +### macOS Compatibility + +LBEM Edition includes fixes for macOS compatibility with BSD getopt. Install GNU getopt via Homebrew and set: +```bash +export FLAGS_GETOPT_CMD="/usr/local/opt/gnu-getopt/bin/getopt" +``` + +## Upstream Projects + +This project is based on and grateful to: + +- **[gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs)** - The CJS Edition that continues maintenance after AVH was archived +- **[gitflow-avh](https://github.com/petervanderdoes/gitflow-avh)** - The AVH Edition that extended the original gitflow +- **[nvie/gitflow](https://github.com/nvie/gitflow)** - The original gitflow implementation by Vincent Driessen + +## Why another git-flow fork? -## Why another git-flow fork The last commit to [gitflow-avh](https://github.com/petervanderdoes/gitflow-avh) was on May 23, 2019 and has been archived on Jun 19, 2023. Since 2019 there have -been a number of issues opened that have not be resolved. This fork will address -those outstanding issues and open PR's along with continuing to maintain the -git-flow branching model. +been a number of issues opened that have not be resolved. [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs) continues maintenance but we needed additional workflow features for our team. ## Getting started @@ -30,14 +60,14 @@ A quick cheatsheet was made by Daniel Kummer: ## Installing git-flow -See the Wiki for up-to-date [Installation Instructions](https://github.com/CJ-Systems/gitflow-cjs/wiki/Installation). +See the Wiki for up-to-date [Installation Instructions](https://github.com/LBEM-CH/gitflow-lbem/wiki/Installation). ## Integration with your shell For those who use the [Bash](https://www.gnu.org/software/bash/) or [ZSH](https://www.zsh.org/) -shell, you can use my [fork of git-flow-completion](https://github.com/petervanderdoes/git-flow-completion) -which includes several additions for git-flow (AVH Edition), or you can use the +shell, you can use our [fork of git-flow-completion](https://github.com/LBEM-CH/gitflow-lbem-completion) +which includes several additions for git-flow (CJS & AVH Edition), or you can use the original [git-flow-completion](https://github.com/bobthecow/git-flow-completion) project by [bobthecow](https://github.com/bobthecow). Both offer tab-completion for git-flow subcommands and branch names with my fork including tab-completion @@ -46,12 +76,13 @@ for the commands not found in the original git-flow. ## FAQ -* See the [FAQ](https://github.com/CJ-Systems/gitflow-cjs/wiki/FAQ) section +* See the [FAQ](https://github.com/LBEM-CH/gitflow-lbem/wiki/FAQ) section of the project Wiki. * Version Numbering Scheme. Starting with version 1.0, the project uses the following scheme: -\.\.\\ +\.\.\-lbem.\ * CJS is the acronym of "CJ Systems" +* LBEM is the acronym of "Laboratory of Biological Electron Microscopy" ## Please help out @@ -72,21 +103,32 @@ using the complete version number. ## Contributing -If submiting a new pull request addressing an already open issue with gitflow-avh please link the relevant issue in the description. For any new issues please see below +### Where to contribute? + +- **LBEM Edition features** (sync, propose, config export, .gitflow file support, finish mode): Please contribute to [gitflow-lbem](https://github.com/LBEM-CH/gitflow-lbem) +- **General gitflow bugs and features**: Please contribute to the upstream [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs) repository. We regularly pull in upstream changes. + +### Quick Start for LBEM Edition contributions -### Quick Start for new issues +* Fork and clone [gitflow-lbem](https://github.com/LBEM-CH/gitflow-lbem) +* Create a feature branch based off develop: `git flow feature start my-feature` +* Commit your changes to the local branch +* Push your feature branch: `git flow feature publish` +* Create a pull request against the `develop` branch -* Please fork and clone a local copy of [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs). -* Create a seperate issue branch based off develop. -* Commit commit you fix to the local branch. -* Please update your local copy with the latest devlop branch of [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs) -* Rebase develop onto your local branch. -* Push your fix to your fork. -* When ready to submit a pull request. +### Quick Start for general gitflow issues + +* Please fork and clone a local copy of [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs) +* Create a separate issue branch based off develop +* Commit your fix to the local branch +* Please update your local copy with the latest develop branch of [gitflow-cjs](https://github.com/CJ-Systems/gitflow-cjs) +* Rebase develop onto your local branch +* Push your fix to your fork +* Submit a pull request ### How to submit a pull request -For any new PRs releated to gitflow-cjs you can use on of the keywords from [Linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) to automatically close the releated issue. +For any new PRs related to gitflow-cjs you can use one of the keywords from [Linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) to automatically close the related issue. ## License terms @@ -217,6 +259,53 @@ and eventually finish it: git flow hotfix finish ``` +### Sync feature/bugfix branches (LBEM Edition) + +To synchronize your feature branch with the latest changes from the develop branch, use: +```shell +git flow feature sync [] +``` + +This command (inspired by [git-town sync](https://www.git-town.com/commands/sync.html)): +- Stashes any uncommitted changes +- Fetches from the remote +- Rebases (or merges, depending on config) your branch onto the latest develop +- Restores your stashed changes + +You can configure the sync strategy: +```shell +git flow config set syncstrategy rebase # default +git flow config set syncstrategy merge +``` + +### Create pull requests (LBEM Edition) + +Instead of merging locally with `finish`, you can create a pull request: +```shell +git flow feature propose [] +``` + +This will: +- Push your branch to the remote (if not already pushed) +- Create a pull request using `gh` CLI (if available) +- Fall back to opening the PR URL in your browser + +You can also configure `finish` to always create a PR instead of merging: +```shell +git flow config set finishmode propose # always create PR +git flow config set finishmode classic # traditional merge (default) +git flow config set finishmode ask # prompt each time +``` + +### Export/Import configuration (LBEM Edition) + +To share gitflow configuration with your team, export settings to a `.gitflow` file: +```shell +git flow config export +``` + +This creates a `.gitflow` file in your repository root that can be committed. When another developer clones the repo and runs `git flow init`, the settings are automatically imported. + ### Using Hooks and Filters From 3c09681a207294630aa2d387b7e78099167d3922 Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:43:45 +0100 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=A8=20feat:=20update=20workflow=20to?= =?UTF-8?q?=20fetch=20latest=20completion=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/trigger-homebrew.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/trigger-homebrew.yml b/.github/workflows/trigger-homebrew.yml index abf3530..c0ddfb4 100644 --- a/.github/workflows/trigger-homebrew.yml +++ b/.github/workflows/trigger-homebrew.yml @@ -9,11 +9,17 @@ jobs: trigger: runs-on: ubuntu-latest steps: - - name: Trigger Homebrew formula update + - name: Get latest completion version + id: completion_version run: | - curl -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/weindel/homebrew-gitflow-lbem/dispatches \ - -d '{"event_type":"new-release","client_payload":{"tag":"${{ github.ref_name }}"}}' + COMP_VERSION=$(curl -s https://api.github.com/repos/LBEM-CH/gitflow-lbem-completion/tags | jq -r '.[0].name') + echo "version=${COMP_VERSION}" >> $GITHUB_OUTPUT + echo "Latest completion version: ${COMP_VERSION}" + + - name: Trigger Homebrew formula update + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.HOMEBREW_DISPATCH_TOKEN }} + repository: LBEM-CH/homebrew-gitflow-lbem + event-type: main-release + client-payload: '{"version": "${{ github.ref_name }}", "completion_version": "${{ steps.completion_version.outputs.version }}"}' From 350fda12796dd949117860911bd526997791d2a1 Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:47:45 +0100 Subject: [PATCH 6/9] pump version --- git-flow-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-flow-version b/git-flow-version index ceb67af..030fc1b 100644 --- a/git-flow-version +++ b/git-flow-version @@ -40,7 +40,7 @@ # -GITFLOW_VERSION=2.2.1-lbem.1 +GITFLOW_VERSION=2.2.1-lbem.2 initialize() { # A function can not be empty. Comments count as empty. From 9bed7d202fba59e47f62a4cff9ee1c96b8e8e349 Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:51:00 +0100 Subject: [PATCH 7/9] feat(lbem): Add enhanced propose, sync, and release features LBEM Edition Enhancements: Propose command improvements: - Add --draft/-d flag to create draft PRs - Add --assignee/-a flag to set PR assignees - Add --reviewer/-R flag to request reviewers - Add --label/-l flag to add PR labels - Add autolabel support (auto-add branch type as label) - Add 'pr' alias for propose command (git flow feature pr) - All propose flags support config defaults Sync command improvements: - Add --prune flag to clean up stale local branches - Prune safely skips branches with unpushed local commits - Add cmd_sync to release and hotfix branches - Add top-level 'git flow sync' shortcut Release/Hotfix finish improvements: - Add --create-release/-9 flag to create GitHub/GitLab releases - Uses gh/glab CLI tools for release creation - Supports config default via gitflow.release.finish.create-release Safety improvements: - Feature/bugfix finish detects GitHub-merged branches - Safely cleans up local branch when remote was merged via web UI - Prune protects branches with unpushed commits Init improvements: - Add LBEM Edition settings section - Configure draft PRs, create-release, sync prune defaults New configuration options: - gitflow.feature.propose.draft - gitflow.feature.propose.assignee - gitflow.feature.propose.reviewer - gitflow.feature.propose.labels - gitflow.feature.propose.autolabel - gitflow.bugfix.propose.* (same as feature) - gitflow.release.finish.create-release - gitflow.hotfix.finish.create-release - gitflow.sync.prune --- git-flow | 22 +++- git-flow-bugfix | 88 +++++++++++-- git-flow-feature | 88 +++++++++++-- git-flow-hotfix | 97 +++++++++++++- git-flow-init | 98 ++++++++++++++- git-flow-release | 109 +++++++++++++++- gitflow-common | 319 ++++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 791 insertions(+), 30 deletions(-) diff --git a/git-flow b/git-flow index 10c0963..d46e4ef 100755 --- a/git-flow +++ b/git-flow @@ -103,6 +103,7 @@ usage() { echo " version Shows version information." echo " config Manage your git-flow configuration." echo " log Show log deviating from base branch." + echo " sync Sync current branch with its base (shortcut)." echo echo "Try 'git flow help' for details." } @@ -128,8 +129,27 @@ main() { # Sanity checks SUBCOMMAND="$1"; shift - if [ "${SUBCOMMAND}" = "finish" ] || [ "${SUBCOMMAND}" = "delete" ] || [ "${SUBCOMMAND}" = "publish" ] || [ "${SUBCOMMAND}" = "rebase" ]; then + if [ "${SUBCOMMAND}" = "finish" ] || [ "${SUBCOMMAND}" = "delete" ] || [ "${SUBCOMMAND}" = "publish" ] || [ "${SUBCOMMAND}" = "rebase" ] || [ "${SUBCOMMAND}" = "sync" ]; then _current_branch=$(git_current_branch) + + # Handle sync on develop/main branches specially + if [ "${SUBCOMMAND}" = "sync" ]; then + _develop_branch=$(git config --get gitflow.branch.develop 2>/dev/null) + _master_branch=$(git config --get gitflow.branch.master 2>/dev/null) + if [ "${_current_branch}" = "${_develop_branch}" ] || [ "${_current_branch}" = "${_master_branch}" ]; then + # For develop/main, just do a pull + echo "Syncing branch '${_current_branch}'..." + git fetch -q origin || { echo "Could not fetch from origin."; exit 1; } + if git rev-parse --verify "origin/${_current_branch}" >/dev/null 2>&1; then + git merge --ff-only "origin/${_current_branch}" || { echo "Could not fast-forward '${_current_branch}'. You may need to merge manually."; exit 1; } + echo "Branch '${_current_branch}' is now up to date with 'origin/${_current_branch}'." + else + echo "No remote tracking branch for '${_current_branch}'." + fi + exit 0 + fi + fi + if gitflow_is_prefixed_branch "${_current_branch}"; then if startswith "${_current_branch}" $(git config --get gitflow.prefix.feature); then SUBACTION="${SUBCOMMAND}" diff --git a/git-flow-bugfix b/git-flow-bugfix index 0f7db7f..ce1b6c5 100644 --- a/git-flow-bugfix +++ b/git-flow-bugfix @@ -62,6 +62,7 @@ git flow bugfix delete git flow bugfix rename git flow bugfix sync git flow bugfix propose +git flow bugfix pr Manage your bugfix branches. @@ -338,6 +339,23 @@ no-ff! Never fast-forward during the merge BASE_BRANCH=${BASE_BRANCH:-$DEVELOP_BRANCH} git_local_branch_exists "$BASE_BRANCH" || die "The base '$BASE_BRANCH' doesn't exists locally or is not a branch. Can't finish the bugfix branch '$BRANCH'." + # LBEM Edition: Check if branch was already merged on GitHub/GitLab + # This handles the case where user did 'propose', merged on remote, and now runs 'finish' + if flag fetch; then + git_fetch_branch "$ORIGIN" "$BASE_BRANCH" + fi + if ! git_remote_branch_exists "$ORIGIN/$BRANCH"; then + # Remote branch doesn't exist - check if it was merged + if git_is_branch_merged_into "$BRANCH" "$BASE_BRANCH"; then + echo "Branch '$BRANCH' appears to have been merged and deleted on remote." + echo "Cleaning up local branch..." + run_pre_hook "$NAME" "$ORIGIN" "$BRANCH" + gitflow_config_remove_base_branch "$BRANCH" + helper_finish_cleanup + exit 0 + fi + fi + # Detect if we're restoring from a merge conflict if [ -f "$DOT_GIT_DIR/.gitflow/MERGE_BASE" ]; then # @@ -883,27 +901,30 @@ showcommands! Show git commands while executing them # cmd_sync() { OPTIONS_SPEC="\ -git flow bugfix sync [-h] [-p] [] +git flow bugfix sync [-h] [-p] [--prune] [] Synchronize bugfix branch with its base branch (like git-town sync). This will: 1. Stash any uncommitted changes - 2. Fetch updates from origin + 2. Fetch updates from origin (with prune) 3. Update the base branch 4. Rebase or merge the bugfix branch on the updated base (configurable) 5. Push the bugfix branch to origin (if -p flag or config) - 6. Restore stashed changes + 6. Optionally prune local branches that were deleted on remote + 7. Restore stashed changes When is omitted the current branch is used, but only if it's a bugfix branch. -- h,help! Show this help showcommands! Show git commands while executing them p,[no]push Push to origin after sync +[no]prune Prune local branches deleted on remote (default: prompt) " - local did_stash sync_strategy + local did_stash sync_strategy prune_behavior # Define flags DEFINE_boolean 'push' false "push to $ORIGIN after sync" p + DEFINE_boolean 'prune' true "prune local branches deleted on remote" # Override defaults with values from config gitflow_override_flag_boolean "bugfix.sync.push" "push" @@ -911,6 +932,10 @@ p,[no]push Push to origin after sync # Parse arguments parse_args "$@" + # Get prune behavior from config + prune_behavior=$(git config --get gitflow.sync.prune 2>/dev/null) + [ -z "$prune_behavior" ] && prune_behavior="prompt" + # Use current branch if no name is given if [ "$NAME" = "" ]; then gitflow_use_current_branch_name @@ -931,9 +956,9 @@ p,[no]push Push to origin after sync gitflow_stash_save did_stash=$? - # Step 2: Fetch from origin + # Step 2: Fetch from origin (with prune) echo "Fetching from '$ORIGIN'..." - git_do fetch -q "$ORIGIN" || die "Could not fetch from '$ORIGIN'." + git_do fetch -q --prune "$ORIGIN" || die "Could not fetch from '$ORIGIN'." # Step 3: Update base branch if it has a remote counterpart if git_remote_branch_exists "$ORIGIN/$BASE_BRANCH"; then @@ -990,7 +1015,12 @@ p,[no]push Push to origin after sync fi fi - # Step 6: Restore stashed changes + # Step 6: Prune stale local branches + if flag prune; then + gitflow_prune_branches "$prune_behavior" + fi + + # Step 7: Restore stashed changes gitflow_stash_pop $did_stash run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" @@ -1001,6 +1031,9 @@ p,[no]push Push to origin after sync if flag push; then echo "- Bugfix branch '$BRANCH' was pushed to '$ORIGIN'" fi + if flag prune; then + echo "- Stale remote-tracking branches were pruned" + fi echo "- You are now on branch '$(git_current_branch)'" echo } @@ -1010,7 +1043,7 @@ p,[no]push Push to origin after sync # cmd_propose() { OPTIONS_SPEC="\ -git flow bugfix propose [-h] [] +git flow bugfix propose [-h] [-d] [-a ] [-r ] [-l ] [] Create a pull request (or merge request) for bugfix branch . This will: @@ -1021,7 +1054,25 @@ When is omitted the current branch is used, but only if it's a bugfix bra -- h,help! Show this help showcommands! Show git commands while executing them +d,[no]draft Create as draft PR +a,assignee= Assign PR to user (use @me for self) +r,reviewer= Request review from user +l,label= Add labels (comma-separated) " + local _draft _assignee _reviewer _labels + + # Define flags + DEFINE_boolean 'draft' false 'create as draft PR' d + DEFINE_string 'assignee' '' 'assign PR to user' a + DEFINE_string 'reviewer' '' 'request review from user' r + DEFINE_string 'label' '' 'add labels (comma-separated)' l + + # Override defaults with values from config + gitflow_override_flag_boolean "bugfix.propose.draft" "draft" + gitflow_override_flag_string "bugfix.propose.assignee" "assignee" + gitflow_override_flag_string "bugfix.propose.reviewer" "reviewer" + gitflow_override_flag_string "bugfix.propose.labels" "label" + # Parse arguments parse_args "$@" @@ -1049,8 +1100,18 @@ showcommands! Show git commands while executing them git_do push "$ORIGIN" "$BRANCH" || die "Could not push '$BRANCH' to '$ORIGIN'." fi - # Create PR - gitflow_create_pr "$BRANCH" "$BASE_BRANCH" "bugfix" + # Convert flag values + if flag draft; then + _draft="true" + else + _draft="false" + fi + _assignee="$FLAGS_assignee" + _reviewer="$FLAGS_reviewer" + _labels="$FLAGS_label" + + # Create PR with options + gitflow_create_pr "$BRANCH" "$BASE_BRANCH" "bugfix" "$_draft" "$_assignee" "$_reviewer" "$_labels" run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" @@ -1060,4 +1121,11 @@ showcommands! Show git commands while executing them echo "- A pull request was created (or browser opened) targeting '$BASE_BRANCH'" echo "- You are now on branch '$(git_current_branch)'" echo +} + +# +# LBEM Edition: PR alias for propose +# +cmd_pr() { + cmd_propose "$@" } \ No newline at end of file diff --git a/git-flow-feature b/git-flow-feature index 17863ba..3046cc8 100644 --- a/git-flow-feature +++ b/git-flow-feature @@ -63,6 +63,7 @@ git flow feature rename git flow feature release git flow feature sync git flow feature propose +git flow feature pr Manage your feature branches. @@ -339,6 +340,23 @@ no-ff! Never fast-forward during the merge BASE_BRANCH=${BASE_BRANCH:-$DEVELOP_BRANCH} git_local_branch_exists "$BASE_BRANCH" || die "The base '$BASE_BRANCH' doesn't exists locally or is not a branch. Can't finish the feature branch '$BRANCH'." + # LBEM Edition: Check if branch was already merged on GitHub/GitLab + # This handles the case where user did 'propose', merged on remote, and now runs 'finish' + if flag fetch; then + git_fetch_branch "$ORIGIN" "$BASE_BRANCH" + fi + if ! git_remote_branch_exists "$ORIGIN/$BRANCH"; then + # Remote branch doesn't exist - check if it was merged + if git_is_branch_merged_into "$BRANCH" "$BASE_BRANCH"; then + echo "Branch '$BRANCH' appears to have been merged and deleted on remote." + echo "Cleaning up local branch..." + run_pre_hook "$NAME" "$ORIGIN" "$BRANCH" + gitflow_config_remove_base_branch "$BRANCH" + helper_finish_cleanup + exit 0 + fi + fi + # Detect if we're restoring from a merge conflict if [ -f "$DOT_GIT_DIR/.gitflow/MERGE_BASE" ]; then # @@ -915,27 +933,30 @@ showcommands! Show git commands while executing them # cmd_sync() { OPTIONS_SPEC="\ -git flow feature sync [-h] [-p] [] +git flow feature sync [-h] [-p] [--prune] [] Synchronize feature branch with its base branch (like git-town sync). This will: 1. Stash any uncommitted changes - 2. Fetch updates from origin + 2. Fetch updates from origin (with prune) 3. Update the base branch 4. Rebase or merge the feature branch on the updated base (configurable) 5. Push the feature branch to origin (if -p flag or config) - 6. Restore stashed changes + 6. Optionally prune local branches that were deleted on remote + 7. Restore stashed changes When is omitted the current branch is used, but only if it's a feature branch. -- h,help! Show this help showcommands! Show git commands while executing them p,[no]push Push to origin after sync +[no]prune Prune local branches deleted on remote (default: prompt) " - local did_stash sync_strategy + local did_stash sync_strategy prune_behavior # Define flags DEFINE_boolean 'push' false "push to $ORIGIN after sync" p + DEFINE_boolean 'prune' true "prune local branches deleted on remote" # Override defaults with values from config gitflow_override_flag_boolean "feature.sync.push" "push" @@ -943,6 +964,10 @@ p,[no]push Push to origin after sync # Parse arguments parse_args "$@" + # Get prune behavior from config + prune_behavior=$(git config --get gitflow.sync.prune 2>/dev/null) + [ -z "$prune_behavior" ] && prune_behavior="prompt" + # Use current branch if no name is given if [ "$NAME" = "" ]; then gitflow_use_current_branch_name @@ -963,9 +988,9 @@ p,[no]push Push to origin after sync gitflow_stash_save did_stash=$? - # Step 2: Fetch from origin + # Step 2: Fetch from origin (with prune) echo "Fetching from '$ORIGIN'..." - git_do fetch -q "$ORIGIN" || die "Could not fetch from '$ORIGIN'." + git_do fetch -q --prune "$ORIGIN" || die "Could not fetch from '$ORIGIN'." # Step 3: Update base branch if it has a remote counterpart if git_remote_branch_exists "$ORIGIN/$BASE_BRANCH"; then @@ -1022,7 +1047,12 @@ p,[no]push Push to origin after sync fi fi - # Step 6: Restore stashed changes + # Step 6: Prune stale local branches + if flag prune; then + gitflow_prune_branches "$prune_behavior" + fi + + # Step 7: Restore stashed changes gitflow_stash_pop $did_stash run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" @@ -1033,6 +1063,9 @@ p,[no]push Push to origin after sync if flag push; then echo "- Feature branch '$BRANCH' was pushed to '$ORIGIN'" fi + if flag prune; then + echo "- Stale remote-tracking branches were pruned" + fi echo "- You are now on branch '$(git_current_branch)'" echo } @@ -1042,7 +1075,7 @@ p,[no]push Push to origin after sync # cmd_propose() { OPTIONS_SPEC="\ -git flow feature propose [-h] [] +git flow feature propose [-h] [-d] [-a ] [-r ] [-l ] [] Create a pull request (or merge request) for feature branch . This will: @@ -1053,7 +1086,25 @@ When is omitted the current branch is used, but only if it's a feature br -- h,help! Show this help showcommands! Show git commands while executing them +d,[no]draft Create as draft PR +a,assignee= Assign PR to user (use @me for self) +r,reviewer= Request review from user +l,label= Add labels (comma-separated) " + local _draft _assignee _reviewer _labels + + # Define flags + DEFINE_boolean 'draft' false 'create as draft PR' d + DEFINE_string 'assignee' '' 'assign PR to user' a + DEFINE_string 'reviewer' '' 'request review from user' r + DEFINE_string 'label' '' 'add labels (comma-separated)' l + + # Override defaults with values from config + gitflow_override_flag_boolean "feature.propose.draft" "draft" + gitflow_override_flag_string "feature.propose.assignee" "assignee" + gitflow_override_flag_string "feature.propose.reviewer" "reviewer" + gitflow_override_flag_string "feature.propose.labels" "label" + # Parse arguments parse_args "$@" @@ -1081,8 +1132,18 @@ showcommands! Show git commands while executing them git_do push "$ORIGIN" "$BRANCH" || die "Could not push '$BRANCH' to '$ORIGIN'." fi - # Create PR - gitflow_create_pr "$BRANCH" "$BASE_BRANCH" "feature" + # Convert flag values + if flag draft; then + _draft="true" + else + _draft="false" + fi + _assignee="$FLAGS_assignee" + _reviewer="$FLAGS_reviewer" + _labels="$FLAGS_label" + + # Create PR with options + gitflow_create_pr "$BRANCH" "$BASE_BRANCH" "feature" "$_draft" "$_assignee" "$_reviewer" "$_labels" run_post_hook "$NAME" "$ORIGIN" "$BRANCH" "$BASE_BRANCH" @@ -1092,4 +1153,11 @@ showcommands! Show git commands while executing them echo "- A pull request was created (or browser opened) targeting '$BASE_BRANCH'" echo "- You are now on branch '$(git_current_branch)'" echo +} + +# +# LBEM Edition: PR alias for propose +# +cmd_pr() { + cmd_propose "$@" } \ No newline at end of file diff --git a/git-flow-hotfix b/git-flow-hotfix index 8d3c649..0cbbba3 100644 --- a/git-flow-hotfix +++ b/git-flow-hotfix @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -53,6 +54,7 @@ git flow hotfix [list] git flow hotfix start git flow hotfix finish git flow hotfix publish +git flow hotfix sync git flow hotfix delete git flow hotfix rebase git flow hotfix track @@ -389,8 +391,9 @@ S,[no]squash Squash hotfix during merge T,tagname! Use given tag name c,cherrypick Cherry Pick to $DEVELOP_BRANCH instead of merge nodevelopmerge! Don't back-merge develop branch +9,[no]create-release Create a GitHub/GitLab release for the tag " - local opts commit keepmsg remotebranchdeleted localbranchdeleted + local opts commit keepmsg remotebranchdeleted localbranchdeleted release_created # Define flags DEFINE_boolean 'fetch' false "fetch from $ORIGIN before performing finish" F @@ -411,6 +414,7 @@ nodevelopmerge! Don't back-merge develop branch DEFINE_string 'tagname' "" "use the given tag name" T DEFINE_boolean 'cherrypick' false "Cherry Pick to $DEVELOP_BRANCH instead of merge" c DEFINE_boolean 'nodevelopmerge' false "don't merge $BRANCH into $DEVELOP_BRANCH " + DEFINE_boolean 'create-release' false "create a GitHub/GitLab release for the tag" 9 # Override defaults with values from config gitflow_override_flag_boolean "hotfix.finish.fetch" "fetch" @@ -430,6 +434,7 @@ nodevelopmerge! Don't back-merge develop branch gitflow_override_flag_string "hotfix.finish.messagefile" "messagefile" gitflow_override_flag_boolean "hotfix.finish.cherrypick" "cherrypick" gitflow_override_flag_boolean "hotfix.finish.nodevelopmerge" "nodevelopmerge" + gitflow_override_flag_boolean "hotfix.finish.create-release" "create-release" # Parse arguments parse_args "$@" @@ -448,6 +453,7 @@ nodevelopmerge! Don't back-merge develop branch remotebranchdeleted=$FLAGS_FALSE localbranchdeleted=$FLAGS_FALSE + release_created=$FLAGS_FALSE # Handle flags that imply other flags if [ "$FLAGS_signingkey" != "" ]; then @@ -697,6 +703,12 @@ if flag cherrypick; then fi fi + # Create GitHub/GitLab release if requested + if noflag notag && flag create-release; then + gitflow_create_release "$VERSION_PREFIX$TAGNAME" "$VERSION_PREFIX$TAGNAME" + release_created=$FLAGS_TRUE + fi + # Delete branch if noflag keep; then @@ -769,6 +781,9 @@ if flag cherrypick; then echo "- '$BASE_BRANCH' and tags have been pushed to '$ORIGIN'" fi fi + if [ $release_created -eq $FLAGS_TRUE ]; then + echo "- A GitHub/GitLab release was created for '$VERSION_PREFIX$TAGNAME'" + fi echo "- You are now on branch '$(git_current_branch)'" echo @@ -839,6 +854,86 @@ r,[no]remote Delete remote branch echo } +# +# LBEM Edition: Sync command - synchronize hotfix branch with remote +# +cmd_sync() { + OPTIONS_SPEC="\ +git flow hotfix sync [-h] [-p] [] + +Synchronize hotfix branch with its remote counterpart. +This will: + 1. Stash any uncommitted changes + 2. Pull updates from the remote hotfix branch + 3. Push the hotfix branch to origin (if -p flag or config) + 4. Restore stashed changes + +When is omitted the current branch is used, but only if it's a hotfix branch. +-- +h,help! Show this help +showcommands! Show git commands while executing them +p,[no]push Push to origin after sync +" + local did_stash + + # Define flags + DEFINE_boolean 'push' false "push to $ORIGIN after sync" p + + # Override defaults with values from config + gitflow_override_flag_boolean "hotfix.sync.push" "push" + + # Parse arguments + parse_args "$@" + + # Use current branch if no name is given + if [ "$VERSION" = "" ]; then + gitflow_use_current_branch_version + fi + + # Sanity checks + require_branch "$BRANCH" + + run_pre_hook "$VERSION" "$ORIGIN" "$BRANCH" + + # Step 1: Stash uncommitted changes + echo "Syncing hotfix branch '$BRANCH'..." + gitflow_stash_save + did_stash=$? + + # Step 2: Fetch and pull from remote hotfix branch + echo "Fetching from '$ORIGIN'..." + git_do fetch -q "$ORIGIN" || die "Could not fetch from '$ORIGIN'." + + if git_remote_branch_exists "$ORIGIN/$BRANCH"; then + echo "Pulling updates from '$ORIGIN/$BRANCH'..." + git_do pull --rebase -q "$ORIGIN" "$BRANCH" 2>/dev/null || { + warn "Could not pull from '$ORIGIN/$BRANCH'. You may need to resolve conflicts." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Step 3: Push if requested + if flag push; then + echo "Pushing '$BRANCH' to '$ORIGIN'..." + git_do push "$ORIGIN" "$BRANCH" || warn "Could not push to '$ORIGIN'. You may need to push manually." + fi + + # Step 4: Restore stashed changes + gitflow_stash_pop $did_stash + + run_post_hook "$VERSION" "$ORIGIN" "$BRANCH" + + echo + echo "Summary of actions:" + echo "- Hotfix branch '$BRANCH' was synced with '$ORIGIN/$BRANCH'" + if flag push; then + echo "- Hotfix branch '$BRANCH' was pushed to '$ORIGIN'" + fi + echo "- You are now on branch '$(git_current_branch)'" + echo +} + cmd_rename() { OPTIONS_SPEC="\ git flow hotfix rename [] diff --git a/git-flow-init b/git-flow-init index a66be26..39bb8c6 100644 --- a/git-flow-init +++ b/git-flow-init @@ -429,7 +429,7 @@ c:,file= use given config file git_do config $gitflow_config_option gitflow.path.hooks "$hooks_dir" fi - # Automate pre-commit hook install if it exists on git-flow hooks directory + # Automate pre-commit hook install if it exists on git-flow hooks directory hooks_dir=$(git config --get gitflow.path.hooks) hooks_dir=${hooks_dir%%/} if [ -f "$hooks_dir/pre-commit" ]; then @@ -451,10 +451,100 @@ c:,file= use given config file fi fi - # TODO: what to do with origin? -} + # LBEM Edition settings + if flag force || \ + ! git config --get gitflow.feature.finish.mode >/dev/null 2>&1; then + echo + echo "LBEM Edition settings:" + fi + + # Feature finish mode (merge or rebase) + if ! git config --get gitflow.feature.finish.mode >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get gitflow.feature.finish.mode || echo "merge") + printf "Feature finish mode (merge/rebase)? [$default_suggestion] " + if noflag defaults; then + read answer + else + printf "\n" + fi + answer=${answer:-$default_suggestion} + if [ "$answer" = "merge" ] || [ "$answer" = "rebase" ]; then + git_do config $gitflow_config_option gitflow.feature.finish.mode "$answer" + else + warn "Invalid mode '$answer'. Using default 'merge'." + git_do config $gitflow_config_option gitflow.feature.finish.mode "merge" + fi + fi + + # Default draft PR setting + if ! git config --get gitflow.propose.draft >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --bool --get gitflow.propose.draft 2>/dev/null) + [ -z "$default_suggestion" ] && default_suggestion="false" + printf "Create draft PRs by default? (true/false) [$default_suggestion] " + if noflag defaults; then + read answer + else + printf "\n" + fi + answer=${answer:-$default_suggestion} + if [ "$answer" = "true" ] || [ "$answer" = "false" ]; then + git_do config $gitflow_config_option gitflow.propose.draft "$answer" + fi + fi + + # Auto-label PRs with branch type + if ! git config --get gitflow.propose.autolabel >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --bool --get gitflow.propose.autolabel 2>/dev/null) + [ -z "$default_suggestion" ] && default_suggestion="false" + printf "Auto-label PRs with branch type (feature/bugfix)? (true/false) [$default_suggestion] " + if noflag defaults; then + read answer + else + printf "\n" + fi + answer=${answer:-$default_suggestion} + if [ "$answer" = "true" ] || [ "$answer" = "false" ]; then + git_do config $gitflow_config_option gitflow.propose.autolabel "$answer" + fi + fi + + # Create GitHub/GitLab releases on release finish + if ! git config --get gitflow.release.finish.create-release >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --bool --get gitflow.release.finish.create-release 2>/dev/null) + [ -z "$default_suggestion" ] && default_suggestion="false" + printf "Create GitHub/GitLab releases on release finish? (true/false) [$default_suggestion] " + if noflag defaults; then + read answer + else + printf "\n" + fi + answer=${answer:-$default_suggestion} + if [ "$answer" = "true" ] || [ "$answer" = "false" ]; then + git_do config $gitflow_config_option gitflow.release.finish.create-release "$answer" + fi + fi + + # Sync prune behavior + if ! git config --get gitflow.sync.prune >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get gitflow.sync.prune 2>/dev/null) + [ -z "$default_suggestion" ] && default_suggestion="prompt" + printf "Sync prune behavior (prompt/delete/keep)? [$default_suggestion] " + if noflag defaults; then + read answer + else + printf "\n" + fi + answer=${answer:-$default_suggestion} + if [ "$answer" = "prompt" ] || [ "$answer" = "delete" ] || [ "$answer" = "keep" ]; then + git_do config $gitflow_config_option gitflow.sync.prune "$answer" + else + warn "Invalid prune behavior '$answer'. Using default 'prompt'." + git_do config $gitflow_config_option gitflow.sync.prune "prompt" + fi + fi -cmd_help() { + # TODO: what to do with origin? +}cmd_help() { usage exit 0 } diff --git a/git-flow-release b/git-flow-release index 5ca7ead..6e32da9 100644 --- a/git-flow-release +++ b/git-flow-release @@ -11,6 +11,7 @@ # http://github.com/CJ-Systems/gitflow-cjs # # Authors: +# Copyright 2025 LBEM. All rights reserved. # Copyright 2003 CJ Systems. All rights reserved. # Copyright 2012-2019 Peter van der Does. All rights reserved. # @@ -43,10 +44,11 @@ # Called when the base of the release is the $DEVELOP_BRANCH # _finish_from_develop() { - local opts merge_branch commit keepmsg remotebranchdeleted localbranchdeleted compare_refs_result merge_result + local opts merge_branch commit keepmsg remotebranchdeleted localbranchdeleted compare_refs_result merge_result release_created remotebranchdeleted=$FLAGS_FALSE localbranchdeleted=$FLAGS_FALSE + release_created=$FLAGS_FALSE # Update local branches with remote branches if flag fetch; then @@ -198,6 +200,12 @@ _finish_from_develop() { fi fi + # Create GitHub/GitLab release if requested + if noflag notag && flag create-release; then + gitflow_create_release "$VERSION_PREFIX$TAGNAME" "$VERSION_PREFIX$TAGNAME" + release_created=$FLAGS_TRUE + fi + # Delete branch if noflag keep; then @@ -266,6 +274,9 @@ _finish_from_develop() { if flag push; then echo "- '$DEVELOP_BRANCH', '$MASTER_BRANCH' and tags have been pushed to '$ORIGIN'" fi + if [ $release_created -eq $FLAGS_TRUE ]; then + echo "- A GitHub/GitLab release was created for '$VERSION_PREFIX$TAGNAME'" + fi echo "- You are now on branch '$(git_current_branch)'" echo } @@ -276,10 +287,11 @@ _finish_from_develop() { # _finish_base() { - local opts merge_branch commit keepmsg localbranchdeleted remotebranchdeleted + local opts merge_branch commit keepmsg localbranchdeleted remotebranchdeleted release_created remotebranchdeleted=$FLAGS_FALSE localbranchdeleted=$FLAGS_FALSE + release_created=$FLAGS_FALSE # Update local branches with remote branches if flag fetch; then @@ -386,6 +398,12 @@ _finish_base() { fi fi + # Create GitHub/GitLab release if requested + if noflag notag && flag create-release; then + gitflow_create_release "$VERSION_PREFIX$TAGNAME" "$VERSION_PREFIX$TAGNAME" + release_created=$FLAGS_TRUE + fi + echo echo "Summary of actions:" if flag fetch; then @@ -418,6 +436,9 @@ _finish_base() { if flag push; then echo "- '$BASE_BRANCH' and tags have been pushed to '$ORIGIN'" fi + if [ $release_created -eq $FLAGS_TRUE ]; then + echo "- A GitHub/GitLab release was created for '$VERSION_PREFIX$TAGNAME'" + fi echo "- You are now on branch '$(git_current_branch)'" echo } @@ -437,6 +458,7 @@ git flow release start git flow release finish git flow release branch git flow release publish +git flow release sync git flow release track git flow release rebase git flow release delete @@ -840,6 +862,7 @@ S,[no]squash Squash release during merge e,[no]edit The --noedit option can be used to accept the auto-generated message on merging T,tagname! Use given tag name 8,nodevelopmerge! Don't back-merge develop branch +9,[no]create-release Create a GitHub/GitLab release for the tag " # Define flags DEFINE_boolean 'fetch' false "fetch from $ORIGIN before performing finish" F @@ -863,6 +886,7 @@ T,tagname! Use given tag name DEFINE_boolean 'edit' true "accept the auto-generated message on merging" e DEFINE_string 'tagname' "" "use the given tag name" T DEFINE_boolean 'nodevelopmerge' false "don't merge $BRANCH into $DEVELOP_BRANCH " 8 + DEFINE_boolean 'create-release' false "create a GitHub/GitLab release for the tag" 9 # Override defaults with values from config gitflow_override_flag_boolean "release.finish.fetch" "fetch" @@ -884,6 +908,7 @@ T,tagname! Use given tag name gitflow_override_flag_string "release.finish.message" "message" gitflow_override_flag_string "release.finish.messagefile" "messagefile" gitflow_override_flag_boolean "release.finish.nodevelopmerge" "nodevelopmerge" + gitflow_override_flag_boolean "release.finish.create-release" "create-release" # Parse arguments parse_args "$@" @@ -1307,3 +1332,83 @@ r,[no]remote Delete remote branch echo "- You are now on branch '$(git_current_branch)'" echo } + +# +# LBEM Edition: Sync command - synchronize release branch with remote +# +cmd_sync() { + OPTIONS_SPEC="\ +git flow release sync [-h] [-p] [] + +Synchronize release branch with its remote counterpart. +This will: + 1. Stash any uncommitted changes + 2. Pull updates from the remote release branch + 3. Push the release branch to origin (if -p flag or config) + 4. Restore stashed changes + +When is omitted the current branch is used, but only if it's a release branch. +-- +h,help! Show this help +showcommands! Show git commands while executing them +p,[no]push Push to origin after sync +" + local did_stash + + # Define flags + DEFINE_boolean 'push' false "push to $ORIGIN after sync" p + + # Override defaults with values from config + gitflow_override_flag_boolean "release.sync.push" "push" + + # Parse arguments + parse_args "$@" + + # Use current branch if no name is given + if [ "$VERSION" = "" ]; then + gitflow_use_current_branch_version + fi + + # Sanity checks + require_branch "$BRANCH" + + run_pre_hook "$VERSION" "$ORIGIN" "$BRANCH" + + # Step 1: Stash uncommitted changes + echo "Syncing release branch '$BRANCH'..." + gitflow_stash_save + did_stash=$? + + # Step 2: Fetch and pull from remote release branch + echo "Fetching from '$ORIGIN'..." + git_do fetch -q "$ORIGIN" || die "Could not fetch from '$ORIGIN'." + + if git_remote_branch_exists "$ORIGIN/$BRANCH"; then + echo "Pulling updates from '$ORIGIN/$BRANCH'..." + git_do pull --rebase -q "$ORIGIN" "$BRANCH" 2>/dev/null || { + warn "Could not pull from '$ORIGIN/$BRANCH'. You may need to resolve conflicts." + gitflow_stash_pop $did_stash + exit 1 + } + fi + + # Step 3: Push if requested + if flag push; then + echo "Pushing '$BRANCH' to '$ORIGIN'..." + git_do push "$ORIGIN" "$BRANCH" || warn "Could not push to '$ORIGIN'. You may need to push manually." + fi + + # Step 4: Restore stashed changes + gitflow_stash_pop $did_stash + + run_post_hook "$VERSION" "$ORIGIN" "$BRANCH" + + echo + echo "Summary of actions:" + echo "- Release branch '$BRANCH' was synced with '$ORIGIN/$BRANCH'" + if flag push; then + echo "- Release branch '$BRANCH' was pushed to '$ORIGIN'" + fi + echo "- You are now on branch '$(git_current_branch)'" + echo +} diff --git a/gitflow-common b/gitflow-common index 1e4178e..257fcf7 100644 --- a/gitflow-common +++ b/gitflow-common @@ -603,6 +603,98 @@ gitflow_get_propose_open() { echo "$_open" } +# gitflow_get_propose_draft() +# +# Check if PRs should be created as drafts by default +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string "true"|"false" (defaults to "false") +# +gitflow_get_propose_draft() { + local _draft _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _draft=$(git config --bool --get gitflow.${_subcommand}.propose.draft 2>/dev/null) + [ -z "$_draft" ] && _draft=$(git config --bool --get gitflow.propose.draft 2>/dev/null) + [ -z "$_draft" ] && _draft="false" + + echo "$_draft" +} + +# gitflow_get_propose_assignee() +# +# Get default assignee for PRs +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string Assignee (empty string if not set) +# +gitflow_get_propose_assignee() { + local _assignee _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _assignee=$(git config --get gitflow.${_subcommand}.propose.assignee 2>/dev/null) + [ -z "$_assignee" ] && _assignee=$(git config --get gitflow.propose.assignee 2>/dev/null) + + echo "$_assignee" +} + +# gitflow_get_propose_reviewer() +# +# Get default reviewer for PRs +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string Reviewer (empty string if not set) +# +gitflow_get_propose_reviewer() { + local _reviewer _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _reviewer=$(git config --get gitflow.${_subcommand}.propose.reviewer 2>/dev/null) + [ -z "$_reviewer" ] && _reviewer=$(git config --get gitflow.propose.reviewer 2>/dev/null) + + echo "$_reviewer" +} + +# gitflow_get_propose_labels() +# +# Get default labels for PRs +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string Labels (comma-separated, empty string if not set) +# +gitflow_get_propose_labels() { + local _labels _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _labels=$(git config --get gitflow.${_subcommand}.propose.labels 2>/dev/null) + [ -z "$_labels" ] && _labels=$(git config --get gitflow.propose.labels 2>/dev/null) + + echo "$_labels" +} + +# gitflow_get_propose_autolabel() +# +# Check if branch type should be auto-added as label +# +# Param $1: string Subcommand name (e.g., "feature", "bugfix") +# Return: string "true"|"false" (defaults to "false") +# +gitflow_get_propose_autolabel() { + local _autolabel _subcommand + _subcommand="${1}" + + # Try subcommand-specific first, then global + _autolabel=$(git config --bool --get gitflow.${_subcommand}.propose.autolabel 2>/dev/null) + [ -z "$_autolabel" ] && _autolabel=$(git config --bool --get gitflow.propose.autolabel 2>/dev/null) + [ -z "$_autolabel" ] && _autolabel="false" + + echo "$_autolabel" +} + # gitflow_detect_forge() # # Detect the forge type (github, gitlab, bitbucket) from remote URL @@ -740,6 +832,112 @@ gitflow_stash_pop() { fi } +# gitflow_prune_branches() +# +# Prune stale remote-tracking branches and optionally delete local branches +# that were tracking branches deleted on remote +# +# Param $1: string Prune behavior ("prompt"|"delete"|"keep") +# +gitflow_prune_branches() { + local _prune_behavior _stale_branches _branch _tracking _remote_branch _answer _skip_all + local _has_unmerged _base_branch + + _prune_behavior="${1:-prompt}" + _skip_all="false" + + # First, prune stale remote-tracking branches + git_do fetch --prune "$ORIGIN" 2>/dev/null + + # Find local branches that were tracking deleted remote branches + _stale_branches="" + for _branch in $(git_local_branches); do + # Get the remote tracking branch + _tracking=$(git config --get "branch.${_branch}.remote" 2>/dev/null) + _remote_branch=$(git config --get "branch.${_branch}.merge" 2>/dev/null) + + # Skip if no tracking info + [ -z "$_tracking" ] || [ -z "$_remote_branch" ] && continue + + # Convert refs/heads/X to X + _remote_branch="${_remote_branch#refs/heads/}" + + # Check if remote branch no longer exists + if [ "$_tracking" = "$ORIGIN" ] && ! git_remote_branch_exists "$ORIGIN/$_remote_branch"; then + _stale_branches="$_stale_branches $_branch" + fi + done + + # If no stale branches, we're done + [ -z "$_stale_branches" ] && return 0 + + # Handle stale branches based on behavior + for _branch in $_stale_branches; do + # Skip protected branches + if [ "$_branch" = "$MASTER_BRANCH" ] || [ "$_branch" = "$DEVELOP_BRANCH" ]; then + continue + fi + + # Skip if we're on this branch + if [ "$_branch" = "$(git_current_branch)" ]; then + warn "Cannot delete branch '$_branch' - currently checked out." + continue + fi + + # Check if branch has unmerged local commits + _base_branch=$(gitflow_config_get_base_branch "$_branch") + _base_branch=${_base_branch:-$DEVELOP_BRANCH} + _has_unmerged="false" + + # Branch has unmerged commits if it's not merged into base + if ! git_is_branch_merged_into "$_branch" "$_base_branch"; then + _has_unmerged="true" + fi + + case "$_prune_behavior" in + delete) + if [ "$_has_unmerged" = "true" ]; then + warn "Branch '$_branch' has unmerged local commits - keeping it safe." + else + echo "Deleting stale local branch '$_branch' (already merged)..." + git_do branch -d "$_branch" 2>/dev/null || warn "Could not delete '$_branch'." + fi + ;; + keep) + echo "Keeping stale local branch '$_branch' (remote was deleted)" + ;; + prompt|*) + if [ "$_skip_all" = "true" ]; then + echo "Keeping stale local branch '$_branch'" + continue + fi + if [ "$_has_unmerged" = "true" ]; then + printf "Branch '%s' was deleted on remote but has LOCAL UNMERGED COMMITS. [d]elete anyway, [k]eep, [s]kip all? " "$_branch" + else + printf "Branch '%s' was deleted on remote (already merged). [d]elete, [k]eep, [s]kip all? " "$_branch" + fi + read _answer + case "$_answer" in + d|D|delete) + if [ "$_has_unmerged" = "true" ]; then + git_do branch -D "$_branch" 2>/dev/null && echo "Force deleted local branch '$_branch'" + else + git_do branch -d "$_branch" 2>/dev/null && echo "Deleted local branch '$_branch'" + fi + ;; + s|S|skip) + _skip_all="true" + echo "Skipping all remaining stale branches" + ;; + *) + echo "Keeping stale local branch '$_branch'" + ;; + esac + ;; + esac + done +} + # gitflow_load_dotgitflow() # # Load settings from .gitflow file in repo root if it exists @@ -770,23 +968,66 @@ gitflow_load_dotgitflow() { # Param $1: string Branch name # Param $2: string Base branch # Param $3: string Subcommand (feature, bugfix) for config +# Param $4: string Draft flag ("true"|"false", optional) +# Param $5: string Assignee (optional, supports @me) +# Param $6: string Reviewer (optional) +# Param $7: string Labels (optional, comma-separated) # gitflow_create_pr() { local _branch _base _subcommand _forge _repo_url _pr_url _open_browser _title + local _draft _assignee _reviewer _labels _autolabel _gh_opts _glab_opts _branch="$1" _base="$2" _subcommand="$3" + + # Get options from parameters or config (parameters take precedence) + _draft="${4:-$(gitflow_get_propose_draft "$_subcommand")}" + _assignee="${5:-$(gitflow_get_propose_assignee "$_subcommand")}" + _reviewer="${6:-$(gitflow_get_propose_reviewer "$_subcommand")}" + _labels="${7:-$(gitflow_get_propose_labels "$_subcommand")}" + _autolabel=$(gitflow_get_propose_autolabel "$_subcommand") + _forge=$(gitflow_detect_forge) _repo_url=$(gitflow_get_repo_url) _open_browser=$(gitflow_get_propose_open "$_subcommand") _title="${_branch#*/}" # Remove prefix for title + + # Add branch type label if autolabel is enabled + if [ "$_autolabel" = "true" ]; then + if [ -n "$_labels" ]; then + _labels="${_labels},${_subcommand}" + else + _labels="$_subcommand" + fi + fi case "$_forge" in github) if gitflow_has_cli_tool "gh"; then echo "Creating pull request using GitHub CLI..." - gh pr create --base "$_base" --head "$_branch" --title "$_title" --fill + + # Build gh options + _gh_opts="--base \"$_base\" --head \"$_branch\" --title \"$_title\" --fill" + + if [ "$_draft" = "true" ]; then + _gh_opts="$_gh_opts --draft" + fi + + if [ -n "$_assignee" ]; then + _gh_opts="$_gh_opts --assignee \"$_assignee\"" + fi + + if [ -n "$_reviewer" ]; then + _gh_opts="$_gh_opts --reviewer \"$_reviewer\"" + fi + + if [ -n "$_labels" ]; then + _gh_opts="$_gh_opts --label \"$_labels\"" + fi + + eval gh pr create $_gh_opts + if [ "$_open_browser" = "true" ]; then gh pr view --web fi @@ -802,7 +1043,28 @@ gitflow_create_pr() { gitlab) if gitflow_has_cli_tool "glab"; then echo "Creating merge request using GitLab CLI..." - glab mr create --source-branch "$_branch" --target-branch "$_base" --title "$_title" --fill + + # Build glab options + _glab_opts="--source-branch \"$_branch\" --target-branch \"$_base\" --title \"$_title\" --fill" + + if [ "$_draft" = "true" ]; then + _glab_opts="$_glab_opts --draft" + fi + + if [ -n "$_assignee" ]; then + _glab_opts="$_glab_opts --assignee \"$_assignee\"" + fi + + if [ -n "$_reviewer" ]; then + _glab_opts="$_glab_opts --reviewer \"$_reviewer\"" + fi + + if [ -n "$_labels" ]; then + _glab_opts="$_glab_opts --label \"$_labels\"" + fi + + eval glab mr create $_glab_opts + if [ "$_open_browser" = "true" ]; then glab mr view --web fi @@ -833,6 +1095,59 @@ gitflow_create_pr() { return 0 } +# gitflow_create_release() +# +# Create a GitHub/GitLab release for a tag +# +# Param $1: string Tag name (e.g., "v1.0.0") +# Param $2: string Title (optional, defaults to tag name) +# Return: 0 on success, 1 on failure +# +gitflow_create_release() { + local _tag _title _forge _repo_url + + _tag="$1" + _title="${2:-$_tag}" + _forge=$(gitflow_detect_forge) + _repo_url=$(gitflow_get_repo_url) + + case "$_forge" in + github) + if gitflow_has_cli_tool "gh"; then + echo "Creating GitHub release for tag '$_tag'..." + gh release create "$_tag" --generate-notes --title "$_title" + return $? + else + echo "GitHub CLI not available. Creating release manually..." + echo "Visit: ${_repo_url}/releases/new?tag=${_tag}" + gitflow_open_url "${_repo_url}/releases/new?tag=${_tag}" + return 0 + fi + ;; + gitlab) + if gitflow_has_cli_tool "glab"; then + echo "Creating GitLab release for tag '$_tag'..." + glab release create "$_tag" --notes "Release $_tag" + return $? + else + echo "GitLab CLI not available. Creating release manually..." + echo "Visit: ${_repo_url}/-/releases/new?tag_name=${_tag}" + gitflow_open_url "${_repo_url}/-/releases/new?tag_name=${_tag}" + return 0 + fi + ;; + bitbucket) + echo "Bitbucket does not have native releases. Tag '$_tag' has been created." + return 0 + ;; + *) + warn "Unknown forge. Cannot create release automatically." + warn "Tag '$_tag' has been created. Please create a release manually if needed." + return 1 + ;; + esac +} + # gitflow_create_squash_message() # # Create the squash message, overriding the one generated by git itself From 2076ed9401ea246d7ebb20e29d286efa0848550b Mon Sep 17 00:00:00 2001 From: Konstantin <30598498+konsdibons@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:51:49 +0100 Subject: [PATCH 8/9] chore: Bump version to 2.2.1-lbem.3 --- git-flow-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-flow-version b/git-flow-version index 030fc1b..38dbb63 100644 --- a/git-flow-version +++ b/git-flow-version @@ -40,7 +40,7 @@ # -GITFLOW_VERSION=2.2.1-lbem.2 +GITFLOW_VERSION=2.2.1-lbem.3 initialize() { # A function can not be empty. Comments count as empty. From 1d6f46444448f724672680597ce590cc8f2f3ab2 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 15 Dec 2025 15:31:51 +0100 Subject: [PATCH 9/9] fix: add missing newline between cmd_default() and cmd_help() functions The closing brace of cmd_default() was concatenated with cmd_help() resulting in '}cmd_help()' which caused a 'Bad function name' syntax error when running with /bin/sh (dash) on Linux systems. --- git-flow-init | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-flow-init b/git-flow-init index 39bb8c6..27dbd3e 100644 --- a/git-flow-init +++ b/git-flow-init @@ -544,7 +544,9 @@ c:,file= use given config file fi # TODO: what to do with origin? -}cmd_help() { +} + +cmd_help() { usage exit 0 }