#!/bin/bash

set -u

##  script for making a git dir with all kinds of files

##  we make two directory structures
##
##  a repo that has modified files:  mods/
##
##    bare/   common bare repo
##    repo1/  base repo for editing
##    repo2/  a repo where we make changes and commit them
##    repo3/  a repo where we make changes that are also in
##                      the working tree and/or index
##
##  in other words, look for interesting statuses in mods/ and
##  repo2/, repo3/

error () {
    1>&2 echo "error: $*"
    exit 1
}

error_cn () {
    error "could not $*"
}

error_cnm () {
    error "could not: mods $*"
}

error_cnc () {
    error "could not: conflicts $*"
}

error_cnl () {
    error "could not: logs $*"
}

random_string () {
    # shellcheck disable=2002
    cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c32
}

initdir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
dir="$initdir/git-test"
## set up directories
rm -rf "$dir" && mkdir "$dir" && cd "$dir" || error_cn "make $dir"


###########################################################################
####  do mods

mods_deleted_arr=()
mkdir mods && cd mods && git init || error_cnm "git init"
echo "unmodified $(random_string)" > unmodified
echo "ignored $(random_string)" > ignored
echo "ignored $(random_string)" > ignored-Ncommit,index_Itree
echo "ignored $(random_string)" > ignored-Icommit,index_Ntree
echo "ignored_mod $(random_string)" > ignored_mod-1Ncommit,index_2Itree

echo $'ignored\nignored-Icommit,index_Ntree\n' > .gitignore
echo "A $(random_string)" > modify-Acommit_Bindex_Ctree
echo "A $(random_string)" > modify-Acommit,index_Btree
echo "A $(random_string)" > modify-Acommit_Bindex,tree
string_modify_Acommit_Bindex_Atree="A $(random_string)"
echo "$string_modify_Acommit_Bindex_Atree" > modify-Acommit_Bindex_Atree
echo "A $(random_string)" > modify-Acommit,index_Xtree
echo "A $(random_string)" > modify-Acommit_Xindex,tree
echo "A $(random_string)" > modify-Acommit_Xindex_Atree
echo "rename $(random_string)" > rename-Acommit,index_Btree-commit
echo "rename $(random_string)" > rename-Acommit_Bindex,tree-A
echo "rename $(random_string)" > rename-Acommit_Bindex_Atree-A
echo "rename $(random_string)" > rename-Acommit_Bindex_Ctree-A
echo "rename $(random_string)" > rename-Acommit_Xindex_Btree-A
git add . && git commit -a -m "Init" || error_cnm "git add and commit"

##  have some things be different in index
echo "A $(random_string)" > modify-Xcommit_Aindex_Xtree
echo "A $(random_string)" > modify-Xcommit_Aindex,tree
echo "A $(random_string)" > modify-Xcommit_Aindex_Btree
echo "B $(random_string)" >> modify-Acommit_Bindex_Ctree
echo "B $(random_string)" > modify-Acommit_Bindex,tree
echo "B $(random_string)" > modify-Acommit_Bindex_Atree
mv rename-Acommit_Bindex_Ctree-A rename-Acommit_Bindex_Ctree-B \
    || error_cnm "mv rename-twice-init rename-twice-index"
mv rename-Acommit_Bindex,tree-A rename-Acommit_Bindex,tree-B \
    || error_cnm "rename"
mv rename-Acommit_Bindex_Atree-A rename-Acommit_Bindex_Atree-B \
    || error_cnm "rename"
git add . || error_cnm "git add ."
git rm modify-Acommit_Xindex,tree
git rm --cached modify-Acommit_Xindex_Atree

git rm --cached rename-Acommit_Xindex_Btree-A
mv rename-Acommit_Xindex_Btree-A rename-Acommit_Xindex_Btree-B

mods_deleted_arr+=(rename-Acommit_Bindex_Ctree-A \
                       rename-Acommit_Bindex,tree-A \
                       modify-Acommit_Xindex,tree \
                       rename-Acommit_Xindex_Btree-A)

##  modify things in working tree
echo $'ignored\nignored-Ncommit,index_Itree\nignored_mod-1Ncommit,index_2Itree\n' > .gitignore
rm modify-Acommit,index_Xtree || error_cnm "remove remove"
mv rename-Acommit,index_Btree-commit rename-Acommit,index_Btree-tree \
    || error_cnm "rename"
echo "A $(random_string)" > modify-Xcommit,index_Atree
echo "B $(random_string)" >> modify-Acommit,index_Btree
echo "$string_modify_Acommit_Bindex_Atree" > modify-Acommit_Bindex_Atree
rm modify-Xcommit_Aindex_Xtree || error_cnm "rm modify-Xcommit_Aindex_Xtree"
echo "C $(random_string)" >> modify-Acommit_Bindex_Ctree
mv rename-Acommit_Bindex_Ctree-B rename_Acommit_Bindex_Ctree-C \
    || error "mods: mv rename-twice-index rename-twice-tree"
mv rename-Acommit_Bindex_Atree-B rename-Acommit_Bindex_Atree-A \
    || error_cnm "rename"
echo "ignored_mod $(random_string)" > ignored_mod-1Ncommit,index_2Itree

mods_deleted_arr+=(modify-Acommit,index_Xtree \
                       rename-Acommit_Bindex_Ctree-B \
                       rename-Acommit_Bindex_Atree-B)
cd ..

###########################################################################
###   do conflicts

## set up bare
mkdir bare && cd bare && git init --bare && cd .. \
        || error_cnc "git init --bare"
## set up repo 1
git clone bare repo1 && cd repo1 || error_cnc "setup repo1"
echo "init $(random_string)" > conflict-commit
echo "init $(random_string)" > conflict-Bcommit_Cindex,tree
echo "init $(random_string)" > conflict-Bcommit_Cindex_Dtree
echo "init $(random_string)" > conflict-index,tree
echo "init $(random_string)" > conflict-Bindex_Ctree
git add . && git commit -a -m "Init" || error_cnc "git add and commit"
git push || error_cnc "git push"
cd .. || error_cnc "cd .."

##  first issues with repo2

## clone from repo 1 to repos 2 at this point. make unmergable change
git clone bare repo2 || error_cnc "make repos 2"
## go back and change some more things in repo1
cd repo1 || error_cnc "cd repo1"
echo "A $(random_string)" > conflict-commit
git add . && git commit -a -m "For repo2" && git push \
        || error_cnc "git add and commit repo1 for repo2 and push"
cd .. || error_cnc "cd .. to 2"

## deal with repo 2 now. just change conflict-commit
cd repo2 || error_cnc "switch to repo2"
echo "B $(random_string)" > conflict-commit
git add . && git commit -a -m "For modify remote" \
        || error_cnc "git add and commit repo2"
git pull
git merge
cd .. || error_cnc "cd .. to 3"

## clone from repo1 to repo 3 at this point. make unmergable change
git clone bare repo3 || error_cnc "make repos 3"
## go back and change some more things in repo1
cd repo1 || error_cnc "cd repo1"
echo "A $(random_string)" > conflict-Bcommit_Cindex,tree
echo "A $(random_string)" > conflict-Bcommit_Cindex_Dtree
string_conflict_Bcommit_Cindex_Atree="A $(random_string)"
echo "$string_conflict_Bcommit_Cindex_Atree" > conflict-Bcommit_Cindex_Atree
git add . && git commit -a -m "For repo3" && git push \
        || error_cnc "git add and commit repo1 for repo3 and push"
cd .. || error_cnc "cd .. to 4"

## deal with repo 3 now.
cd repo3 || error_cnc "switch to repo3"
echo "B $(random_string)" > conflict-Bcommit_Cindex,tree
echo "B $(random_string)" > conflict-Bcommit_Cindex_Dtree
echo "B $(random_string)" > conflict-Bcommit_Cindex_Atree
git add . && git commit -a -m "For modify remote" \
        || error_cnc "git add and commit repo3"

##  have some things be different in index
echo "C $(random_string)" > conflict-Bcommit_Cindex,tree
echo "C $(random_string)" > conflict-Bcommit_Cindex_Dtree
echo "B $(random_string)" > conflict-index,tree
echo "B $(random_string)" > conflict-Bindex_Ctree
echo "C $(random_string)" > conflict-Bcommit_Cindex_Atree
git add . || error_cnc "git add ."

##  have some things be different in tree
echo "D $(random_string)" > conflict-Bcommit_Cindex_Dtree
echo "C $(random_string)" > conflict-Bindex_Ctree
echo "$string_conflict_Bcommit_Cindex_Atree" > conflict-Bcommit_Cindex_Atree
git pull
git merge

logdir="${initdir:?initidir not set}/logs"
rm -rf "$logdir" && mkdir "$logdir" || error_cnl "remake $logdir"

print_git_status () {
    local repo log_pref deleted_arr
    repo="$1"
    cd "$dir/$repo" || error_cnl "change dir $dir/$repo"
    log_pref="$logdir/$repo"
    set -- git status --porcelain --untracked-files --ignored
    # shellcheck disable=2068,2156
    {
        $@ | "$initdir/git-status-sort.sh"  > "$log_pref-plain.log" \
            || error_cnl "make log $log_pref-plain.log"
        find . -maxdepth 1 -mindepth 1 -type f -exec $@ '{}' \; \
            | "$initdir/git-status-sort.sh" > "$log_pref-find.log" \
            || error_cnl "make log $log_pref-find.log"
        find . -maxdepth 1 -mindepth 1 -type f -exec \
             bash -c "echo '{}:' && $* {} && echo" \; \
             > "$log_pref-find-withname.log" \
            || error_cnl "make log $log_pref-find-withname.log"
        ##  add deleted files if present
        if [[ -v "${repo}_deleted_arr" ]]; then
            declare -n deleted_arr="${repo}_deleted_arr"
            for file in "${deleted_arr[@]}"; do
                echo "$file:" >> "$log_pref-deleted-withfilename.log"
                $@ "$file" | tee -a "$log_pref-deleted-withfilename.log" \
                                 >> "$log_pref-deleted.log~"
            done
            "$initdir/git-status-sort.sh" < "$log_pref-deleted.log~" \
                                          > "$log_pref-deleted.log"
        fi
    }
}

print_git_status "mods"
print_git_status "repo2"
print_git_status "repo3"

echo "Success"
