#!/usr/bin/env bash
# vim:ts=4:sts=4:sw=4:et
#
# Author: Hari Sekhon
# Date: circa 2006 (forked from .bashrc)
#
# https://github.com/harisekhon/bash-tools
#
# License: see accompanying Hari Sekhon LICENSE file
#
# If you're using my code you're welcome to connect with me on LinkedIn and optionally send me feedback to help steer this or other code I publish
#
# https://www.linkedin.com/in/harisekhon
#
# ============================================================================ #
# R e v i s i o n C o n t r o l - G i t
# ============================================================================ #
# Primary revision control system
#
# if svn.sh and hg.sh functions are enabled, detects and calls svn and mercurial commands if inside those repos so some of the same commands work dynamically
if [ -f ~/.github_token ] ; then
GITHUB_TOKEN = " $( cat ~/.github_token) "
export GITHUB_TOKEN
fi
if [ -f ~/.gitlab_token ] ; then
GITLAB_API_PRIVATE_TOKEN = " $( cat ~/.gitlab_token) "
export GITLAB_API_PRIVATE_TOKEN
fi
if [ -z " ${ GITLAB_API_ENDPOINT :- } " ] ; then
export GITLAB_API_ENDPOINT = "https://gitlab.com/api/v3"
fi
if ! type basedir & >/dev/null; then
. " $( dirname " ${ BASH_SOURCE [0] } " ) /functions.sh "
fi
# set location where you check out all the github repos
export github = ~/github
export GIT_PAGER = " less ${ LESS :- } "
# shellcheck disable=SC2230
#if [ -z "${GIT_PAGER:-}" ] && \
if type -P diff-so-fancy & >/dev/null; then
# pre-loading a pattern to 'n' / 'N' / '?' / '/' search through will force you in to pager and disregard -F / --quit-if-one-screen
#export GIT_PAGER="diff-so-fancy --color=yes | less -RFX --tabs=4 --pattern '^(Date|added|deleted|modified): '"
export GIT_PAGER = " diff-so-fancy --color=yes | $GIT_PAGER "
fi
alias gitconfig = "\$EDITOR ~/.gitconfig"
alias gitignore = "\$EDITOR ~/.gitignore_global"
alias gitrc = gitconfig
# false positive, not calling this from xargs
# shellcheck disable=SC2032
#alias add=gitadd
add( ) { gitadd " $@ " ; }
alias gadd = 'git add'
alias import = gitimport
alias co = checkout
alias commit = "git commit"
alias clone = "git clone"
alias gitci = commit
alias ci = commit
alias gitco = checkout
alias up = pull
alias u = up
alias pu = push
alias gitp = "git push"
alias gdiff = "git diff"
# bypasses diff-so-fancy, could also just pipe through | cat to disable pager and color effects
alias gdiff2 = "git --no-pager diff"
alias gdiffc = "git diff --cached"
alias gdiffm = "gdiff origin/master.."
alias gd = gdiff
alias gdc = gdiffc
alias gdo = gdiffm
alias branch = "githg branch"
alias br = branch
alias fetch = 'git fetch'
alias stash = "git stash"
alias tag = "githg tag"
alias um = updatemodules
#type browse &>/dev/null || alias browse=gbrowse
alias gbrowse = gitbrowse
alias gh = 'gitbrowse "" github'
alias github_actions = 'gitbrowse actions github'
alias github_workflows = 'github_actions'
alias gha = 'github_actions'
alias ghw = 'github_workflows'
alias wf = 'cd .github/workflows/'
alias ggrep = "git grep"
alias remotes = 'git remote -v'
alias remote = 'remotes'
# much quicker to just 'cd $github; f <pattern>'
#githubls(){
# # GitHub is svn compatible, use this to list files remotely
# svn ls "https://github.com/$1.git/branches/master/"
#}
#githubgrep(){
# for repo in $(sed 's/#.*//;s/:.*//;/^[[:space:]]*$/d' "$srcdir/setup/repolist.txt"); do
# githubls "HariSekhon/$repo"
# done |
# grep "$@"
#}
# git fetch -p or git remote prune origin
alias prune = "co master; git pull --no-edit; git remote prune origin; git branch --merged | grep -v -e '^\\*' -e 'master' | xargs git branch -d"
# don't use this unless you are a git pro and understand unwinding history and merge conflicts
alias GRH = "git reset HEAD^"
alias master = "switchbranch master"
alias prod = "switchbranch prod"
alias staging = "switchbranch staging"
alias stage = staging
alias dev = "switchbranch dev"
# equivalent of hg root
git_root( ) {
git rev-parse --show-toplevel
}
gitgc( ) {
if ! [ -d .git ] ; then
echo "not at top of a git repo, not .git/ directory found"
return 1
fi
du -sh .git
git gc --aggressive
du -sh .git
}
gitbrowse( ) {
local url
local filter = " ${ 2 :- .* } "
url = " $( git remote -v | grep " $filter " | awk '/git@|https:/{print $2}' | sed 's,://.*@,://,; s|git@github.com:|https://github.com/| ; s/\.git$//' | head -n1) "
if [ $# -gt 0 ] &&
[ -z " $url " ] ; then
echo "git remote url not found"
return 1
fi
browser " $url / $1 "
}
install_git_completion( ) {
if ! [ -f ~/.git-completion.bash ] ; then
wget -O ~/.git-completion.bash https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash
fi
}
# shellcheck disable=SC1090
[ -f ~/.git-completion.bash ] && . ~/.git-completion.bash
# usage: gi python,perl,go
# gi list
gitignore_api( ) {
local url
local langs
local options = ( )
local args = ( )
# noop - set to use 'tr' to separate items to newlines when given the 'list' arg
local commas_to_newlines = "cat"
for arg; do
if [ " $arg " = -- ] ; then
options += ( " $arg " )
else
args += ( " $arg " )
fi
done
# take args 'python perl', store as 'python,perl' for the API call
langs = " $( IFS = , ; echo " ${ args [*] } " ) "
url = " https://www.gitignore.io/api/ $langs "
if [ " $langs " = "list" ] ; then
commas_to_newlines = "tr ',' '\\n'"
fi
{
if hash curl 2>/dev/null; then
curl -sSL " ${ options [*] } " " $url "
elif hash wget 2>/dev/null; then
wget -O - " ${ options [*] } " " $url "
fi
} | eval " $commas_to_newlines "
echo
}
alias gi = gitignore_api
git_user_repo( ) {
git remote -v | awk '{print $2}' | head -n1 | sed 's|.*\.[^:/]*[:/]||'
}
git_repo( ) {
git_user_repo | sed 's|.*/||'
}
github_user_repo( ) {
git remote -v | awk '/github.com/{print $2}' | head -n1 | sed 's|.*\.[^:/]*[:/]||'
}
github_repo( ) {
github_user_repo | sed 's|.*/||'
}
git_repo_strip( ) {
sed 's/[[:alnum:]]*@//'
}
isGit( ) {
local target = ${ 1 :- . }
# There aren't local .hg dirs everywhere only at top level so this is difficult in bash
if [ -d " $target /.git " ] ; then
return 0
elif [ -f " $target " ] &&
[ -d " ${ target %/* } /.git " ] ; then
#-o "$target/../.git" -o "${target%/*}/../.git" ]; then
return 0
else
# This is because git command doesn't return correctly when running from outside git root, complains there is not .git
pushd " $( dirname " $target " ) " >/dev/null || return 1
# subdirs which are not handled by Git fail isGit
# returns false for a newly added not committed dir
#if git log -1 "$target" 2>/dev/null | grep -q '.*'; then
if [ -n " $( git log -1 " $( basename " $target " ) " 2>/dev/null) " ] ; then
# shellcheck disable=SC2164
popd & >/dev/null
return 0
fi
# shellcheck disable=SC2164
popd & >/dev/null
return 2
fi
}
st( ) {
# shellcheck disable=SC2086
{
local target = " ${ 1 :- . } "
shift
if ! [ -e " $target " ] ; then
echo " $target does not exist "
return 1
fi
local target_basename
local target_dirname
target_basename = " $( basename " $target " ) "
target_dirname = " $( dirname " $target " ) "
#if [ -f "Vagrantfile" ]; then
# echo "> vagrant status"
# vagrant status
# shellcheck disable=SC2166
if [ " $target_basename " = "github" ] ||
[ " $target " = "." -a " $( pwd ) " = ~/github ] ; then
hr
for x in " $target " /*; do
[ -d " $x " ] || continue
pushd " $x " >/dev/null || { echo " failed to pushd to ' $x ' " ; return 1; }
if git remote -v | grep -qi harisekhon; then
echo " > GitHub: git status $x $* "
git status . " $@ "
echo
hr
echo
fi
# shellcheck disable=SC2164
popd & >/dev/null
done
elif isGit " $target " ; then
if [ -d " $target " ] ; then
pushd " $target " >/dev/null || { echo " Error: failed to pushd to $target " ; return 1; }
echo "> git stash list" >& 2
git stash list && echo
#"$bash_tools/git_summary_line.sh"
echo " > git status $target $* " >& 2
#git -c color.status=always status -sb . "$@"
git -c color.status= always status . " $@ "
else
pushd " $target_dirname " >/dev/null || { echo " Error: failed to pushed to ' $target_dirname ' " ; return 1; }
echo " > git status $target $* " >& 2
#"$bash_tools/git_summary_line.sh"
git -c color.status= always status " $target_basename " " $@ "
fi
#git status "$target" "${*:2}"
# shellcheck disable=SC2164
popd & >/dev/null
elif type isHg & >/dev/null && isHg " $target " ; then
echo " > hg status $target $* " >& 2
hg status " $target " " $@ " | grep -v "^?"
# to see relative paths instead of the default absolute paths
#hg status "$(hg root)"
elif type isSvn & >/dev/null && isSvn " $target " ; then
echo " > svn st $* " >& 2
svn st --ignore-externals " $target " " $@ " | grep -v -e "^?" -e "^x" ;
else
echo "not a revision controlled resource as far as bashrc can tell"
fi
} |
# more calls less on Mac, and gets stuck in interactive mode ignoring the less alias switches
#more -R -n "$((LINES - 3))"
#less -RFX
eval ${ GIT_PAGER :- cat }
}
stq( ) {
st " $@ " | grep --color= no -e "=======" -e branch -e GitHub | eval " ${ GIT_PAGER :- cat } "
}
# disabling this as I don't use Mercurial or Svn any more,
# replacing with simpler function below that will pass through more things like --rebase
#pull(){
# local target="${1:-.}"
# if ! [ -e "$target" ]; then
# echo "$target does not exist"
# return 1
# fi
# local target_basename
# target_basename="$(basename "$target")"
# # shellcheck disable=SC2166
# if [ "$target_basename" = "github" ] || [ "$target" = "." -a "$(pwd)" = "$github" ]; then
# for x in "$target"/*; do
# [ -d "$x" ] || continue
# # get last character of string
# [ "${x: -1}" = 2 ] && continue
# pushd "$x" >/dev/null || { echo "failed to pushd to '$x'"; return 1; }
# if git remote -v | grep -qi harisekhon; then
# echo "> GitHub: git pull $x ${*:2}"
# git pull "${@:2}"
# echo
# echo "> GitHub: git submodule update --init --recursive"
# git submodule update --init --recursive
# echo
# fi
# # shellcheck disable=SC2164
# popd &>/dev/null
# done
# return
# elif isGit "$target"; then
# pushd "$target" >/dev/null &&
# echo "> git pull -v ${*:2}" >&2
# git pull -v "${@:2}"
# echo "> git submodule update --init --recursive"
# git submodule update --init --recursive
# #local orig_branch=$(git branch | awk '/^\*/ {print $2}')
# #for branch in $(git branch | cut -c 3- ); do
# # git checkout -q "$branch" &&
# # echo -n "$branch => " &&
# # git pull -v
# # echo
# # echo
# #done
# #git checkout -q "$orig_branch"
# # shellcheck disable=SC2164
# popd &>/dev/null
# elif type isHg &>/dev/null && isHg "$target"; then
# pushd "$target" >/dev/null &&
# echo "> hg pull && hg up" >&2 &&
# hg pull && hg up
# # shellcheck disable=SC2164
# popd &>/dev/null
# elif type isSvn &>/dev/null && isSvn "$target"; then
# echo "> svn up $target" >&2
# svn up "$target"
# else
# echo "not a revision controlled resource as far as bashrc can tell"
# return 1
# fi
#}
# simpler replacement function to above
pull( ) {
# shellcheck disable=SC2166
if [ " ${ PWD ##*/ } " = github ] ; then
for x in *; do
[ -d " $x " ] || continue
# get last character of string - don't pull blah2, as I use them as clean checkouts
[ " ${ x : -1 } " = 2 ] && continue
pushd " $x " >/dev/null || { echo " failed to pushd to ' $x ' " ; return 1; }
if git remote -v | grep -qi " ${ GITHUB_USER :- ${ GIT_USER :- ${ USER :- } } } " ; then
echo " > GitHub $x : git pull --all --no-edit $* "
git pull --all --no-edit " $@ "
echo
echo " > GitHub $x : git submodule update --init --recursive "
git submodule update --init --recursive
echo
fi
# shellcheck disable=SC2164
popd & >/dev/null
done
return
else
echo " > git pull --all --no-edit $* "
git pull --all --no-edit " $@ "
echo "> git submodule update --init --recursive"
git submodule update --init --recursive
fi
}
checkout( ) {
if isGit "." ; then
git checkout " $@ " ;
else
echo " not a Git checkout, cannot switch to branch $* "
return 1
fi
}
_gitaddimport( ) {
local action = " $1 "
shift;
[ -z " $* " ] && return 1
local basedir;
basedir = " $( basedir " $@ " ) " && local trap_codes = "INT ERR" ;
# shellcheck disable=SC2064,SC2086
trap " popd &>/dev/null; trap - $trap_codes ; return 1 2>/dev/null " $trap_codes ;
pushd " $basedir " > /dev/null || return 1;
# shellcheck disable=SC2086
targets = " $( strip_basedirs " $basedir " " $@ " ) " ;
for x in $targets ; do
if git status -s " $x " | grep -q '^[?A]' ; then
git add " $x " &&
git commit -m " $action $x " " $x "
elif git status -s " $x " | grep -q '^.M' ; then
echo " ' $x ' already in git, but has changes, commit as an update instead " >& 2
else
echo " ' $x ' already in git " >& 2
fi
done
popd > /dev/null || :
}
gitadd( ) {
_gitaddimport added " $@ "
}
gitimport( ) {
_gitaddimport imported " $@ "
}
# shellcheck disable=SC2086
gitu( ) {
if [ -z " $1 " ] ; then
echo "usage: gitu <file>"
return 3
fi
local targets
if [ -n " $( git diff " $@ " 2>/dev/null || :) " ] ; then
targets = " $* "
else
# follow symlinks to the actual files because diffing symlinks returns no changes
targets = " $( resolve_symlinks " $@ " ) "
fi
local basedir
# go to the highest directory level to git diff inside the git repo boundary, otherwise git diff will return nothing
basedir = " $( basedir $targets ) " &&
local trap_codes = "INT ERR"
# expand now
# shellcheck disable=SC2064
trap " popd &>/dev/null; trap - $trap_codes ; return 1 2>/dev/null " $trap_codes
pushd " $basedir " >/dev/null || return 1
targets = " $( strip_basedirs " $basedir " $targets ) "
# shellcheck disable=SC2086
if [ -z " $( git diff $targets ) " ] ; then
popd & >/dev/null || :
return 0
fi
# shellcheck disable=SC2086
git diff $targets &&
read -r -p "Hit enter to commit or Control-C to cancel" &&
git add $targets &&
echo " committing $targets " &&
git commit -m " updated $targets " $targets
popd & >/dev/null || :
trap - $trap_codes
}
#githgu(){
# target="${1:-.}"
# #count=0
# while [ -L "$target" ]; do
# #target="$(readlink "$target")"
# #let count+=1
# #if [ $count -gt 10 ]; then
# # echo "looping over links more than 10 times in hggitu! "
# # exit 2
# #fi
# echo "$target is a symlink! "
# return 1
# done
# if ! [ -e "$target" ]; then
# echo "$target does not exist"
# return 1
# fi
# if isGit "$target"; then
# echo "> git" >&2
# #if [ -d "$target" ]; then
# # pushd "$target" >/dev/null
# #else
# # pushd "$(dirname "$target")" >/dev/null
# #fi
# #"$srcdir2/gitu" "${target##*/}" &&
# gitu "$target"
# #popd &>/dev/null
# elif type isHg &>/dev/null && isHg "$target"; then
# echo "> hg" >&2
# #if [ -d "$target" ]; then
# # pushd "$target" >/dev/null
# #else
# # pushd "$(dirname "$target")" >/dev/null
# #fi
# #"$srcdir2/hgu" "${target##*/}" &&
# hgu "$target"
# #popd &>/dev/null
# # Not supporting SVN any more
# #elif type isSvn &>/dev/null && isSvn "$target"; then
# # echo "> svn" >&2
# # svnu "$target"
# else
# echo "not a revision controlled resource as far as bashrc can tell"
# return 1
# fi
#}
push( ) {
pull " $@ " || return 1
if isGit .; then
echo " > git push -v $* "
#for remote in $(git remote); do
# git push -v $remote $@
#done
git push -v " $@ "
elif type isHg & >/dev/null && isHg .; then
echo " > hg push $* "
hg push " $@ "
else
echo "not in a Git or Mercurial controlled directory"
return 1
fi
}
switchbranch( ) {
if isGit "." ; then
git checkout " $1 " ;
elif type isHg & >/dev/null && isHg "." ; then
hg update " $1 "
else
echo " not a Git / Mercurial checkout, cannot switch to branch $1 "
return 1
fi
}
gitrm( ) {
git rm " $@ " &&
git commit -m " removed $* " " $@ "
}
gitrename( ) {
git mv " $1 " " $2 " &&
git commit -m " renamed $1 to $2 " " $1 " " $2 "
}
gitmv( ) {
git mv " $1 " " $2 " &&
git commit -m " moved $1 to $2 " " $1 " " $2 "
}
gitd( ) {
git diff " ${ @ :- . } "
}
gitadded( ) {
git log --name-status " $@ " |
grep -e '^A[^u]' -e '^Date' |
grep -B 1 '^A' |
less
}
# doesn't need pipe | less, git drops you in to less anyway
gitl( ) {
git log --all --name-status --graph --decorate " $@ "
}
gitlp( ) {
git log -p " $@ "
}
gitl2( ) {
git log --pretty= format:"%n%an => %ar%n%s" --name-status " $@ "
}
githg( ) {
if isGit .; then
git " $@ "
elif type isHg & >/dev/null && isHg .; then
hg " $@ "
else
echo "not a Git/Mercurial checkout"
return 1
fi
}
retag( ) {
local tag1 = " $1 "
local checksum = " $2 "
local additional_tags = " ${ * : 2 } "
for tag in $tag1 $additional_tags ; do
git tag -d " $tag " || :
echo " Creating git tag ' $tag ' "
# quoting checksum causes failure with unrecognized checksum ''
git tag " $tag " " $checksum "
git tag |
grep -qF " $tag " ||
echo "FAILED"
done
}
gitfind( ) {
local refids
refids = " $( git log --all --oneline | grep " $@ " | awk '{print $1}' ) "
printf 'Branches:\n\n'
for refid in $refids ; do
git branch --contains " $refid "
done | sort -u
printf '\nTags:\n\n'
for refid in $refids ; do
git tag --contains " $refid "
done | sort -u
}
# useful for smaller things:
#
# git submodule foreach --recursive 'git checkout master && git pull'
#
updatemodules( ) {
if isGit .; then
git pull --no-edit
#git submodule update --init --remote
for submodule in $( git submodule | awk '{print $2}' ) ; do
if [ -d " $submodule " ] && ! [ -L " $submodule " ] ; then
pushd " $submodule " || continue
git stash
git checkout master
git pull --no-edit
git submodule update
# shellcheck disable=SC2164
popd
fi
done
echo
for submodule in $( git submodule | awk '{print $2}' ) ; do
if [ -d " $submodule " ] && ! [ -L " $submodule " ] && ! git status " $submodule " | grep -q nothing; then
git commit -m " updated $submodule " " $submodule " || break
fi
done &&
make updatem ||
echo FAILED
echo
for submodule in $( git submodule | awk '{print $2}' ) ; do
if [ -d " $submodule " ] && ! [ -L " $submodule " ] ; then
pushd " $submodule " || continue
git stash pop
# shellcheck disable=SC2164
popd
fi
done
else
echo "Not a Git repository! "
return 1
fi
}
upl( ) {
local repos = "pylib pytools lib tools bash-tools nagios-plugins npk"
# pull all repos first so can handle merge requests if needed
for repo in $repos ; do
echo
echo " * Pulling latest repo changes: $repo "
echo
pushd " $github / $repo " &&
git pull --no-edit &&
popd &&
hr || return 1
done
echo
echo "UNATTEND FROM HERE"
echo
for repo in $repos ; do
echo
echo " * Performing latest submodule updates: $repo "
echo
pushd " $github / $repo " &&
! updatemodules 2>& 1 | tee /dev/stderr | grep -e ERROR -e FAIL &&
git push &&
popd &&
hr || return 1
done
}
#stagemerge(){
# if isGit "."; then
# git checkout prod && git pull &&
# git checkout staging && git pull &&
# git merge prod
# git checkout prod
# else
# echo "Not a Git working copy";
# fi
#}
gitdiff( ) {
local filename = " ${ 1 :- } "
[ -n " $filename " ] || { echo "usage: gitdiff filename" ; return 1; }
git diff " $filename " > "/tmp/gitdiff.tmp"
diffnet.pl "/tmp/hgdiff.tmp"
}
git_author_names( ) {
git log --all --pretty= format:"%an" | sort | uniq -c | sort -k1nr | less
}
git_author_emails( ) {
git log --all --pretty= format:"%ae" | sort | uniq -c | sort -k1nr | less
}
git_author_names_emails( ) {
git log --all --pretty= format:"%an %ae" | sort | uniq -c | sort -k1nr | less
}
git_authors( ) {
git_author_emails
}
git_commit_count( ) {
# interestingly, even on 10,000 commit repos, there are no duplicate short hashes shown from:
# git log --all --pretty=format:"%h" | sort | uniq -d
git log --all --pretty= format:"%h" | wc -l
}
git_revert_typechange( ) {
# want splitting to separate filenames
# shellcheck disable=SC2046
co $( git status --porcelain -s " ${ 1 :- . } " | awk '/^.T/{print $2}' )
}
git_rm_untracked( ) {
if [ $# -lt 1 ] ; then
echo "usage: rm_untracked <target_dir_or_files_or_glob>"
return 1
fi
# iterate on explicit targets only
# intentionally not including current directory to avoid accidentally wiping out untracked files - you must specify "rm_untracked ." if you really intend this
for x in " ${ @ :- } " ; do
git status --porcelain -s --untracked-files= all " $x " |
# this breaks the correct spacings for Spotify playlist filenames
#awk '/^\?\?/{$1=""; print}' |
grep '^??' |
sed 's/^?? //' |
while read -r filename; do
# git status --porcelain double quotes file paths when containing unicode chars which are representated in \xxx format
# you must set 'git config --global core.quotePath false' for this to work properly
#
# this doesn't help because you are still stuck with \xxx chars throughout
#filename="${filename#\"}"
#filename="${filename%\"}"
rm -v " $filename " || break
done
done
}
# example of usage of this in the function below - make sure to put '$repo' or "\$repo" somewhere in the argument body to make use of the iteration variable
foreachrepo( ) {
local repolist = " ${ REPOLIST :- $bash_tools /setup/repolist.txt } "
while read -r repo; do
eval " $@ "
done < <( sed 's/#.*$//; s/.*://; /^[[:space:]]*$/d' " $repolist " )
}
github_authors( ) {
# deferring expansion into loop
# shellcheck disable=SC2016
foreachrepo 'echo "repo: $repo"; pushd "$github/$repo" >/dev/null || return 1; git_authors; popd >/dev/null || return 1; echo' | ${ less :- less }
}
merge_conflicting_files( ) {
# merge conflicts:
#
# UU = both updated
# AA = both added
#
git status --porcelain | awk '/^UU|^AA/{$1=""; print}'
}
merge_deleted_files( ) {
git status --porcelain | awk '/^DU/{$1=""; print}'
}
# useful for Dockerfiles merging lots of branches
#
# while ! make mergemasterpull; do fixmerge "merged master"; done
#
fixmerge( ) {
local msg = " ${ * :- merged } "
local merge_conflicted_files
local merge_deleted_files
merge_deleted_files = " $( merge_deleted_files) "
if [ -n " $merge_deleted_files " ] ; then
# false positive, not passing add function/alias add to git
# shellcheck disable=SC2033
xargs git add <<< " $merge_deleted_files "
fi
merge_conflicted_files = " $( merge_conflicting_files) "
if [ -n " $merge_conflicted_files " ] ; then
# shellcheck disable=SC2086
" $EDITOR " $merge_conflicted_files &&
git add $merge_conflicted_files
fi
git ci -m " $msg "
}