From c47f3e840121f395ab45c41fdadabc79dcdfe027 Mon Sep 17 00:00:00 2001 From: Domen Tabernik Date: Mon, 15 Jun 2026 11:27:20 +0200 Subject: [PATCH] Handle stale inventory git conflicts --- clean_inventory.sh | 43 +++++++++++++++++++++++++++++++++ commit.py | 42 +++++++++++++++++++++++++++------ commit.sh | 59 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 123 insertions(+), 21 deletions(-) create mode 100755 clean_inventory.sh mode change 100644 => 100755 commit.sh diff --git a/clean_inventory.sh b/clean_inventory.sh new file mode 100755 index 0000000..b0dd7e5 --- /dev/null +++ b/clean_inventory.sh @@ -0,0 +1,43 @@ +#! /usr/bin/env bash +set -u + +lockfile=/opt/ccc-manager/commit.lock +inventory_dir=${CCC_INVENTORY_DIR:-/opt/ccc-inventory} + +function cleanup() { + rm -f "$lockfile" +} + +function error() { + echo "$1" + cleanup + exit 1 +} + +if [[ -e "$lockfile" ]]; then + error 'Could not acquire lock. Try again.' +fi +touch "$lockfile" +trap cleanup EXIT + +cd "$inventory_dir" || error "Inventory directory not found: $inventory_dir" + +git rebase --abort >/dev/null 2>&1 || true +git merge --abort >/dev/null 2>&1 || true + +upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true) +if [[ -z "$upstream" ]]; then + branch=${CCC_INVENTORY_BRANCH:-$(git branch --show-current)} + if [[ -z "$branch" ]]; then + error 'Could not determine inventory branch to reset' + fi + upstream="origin/$branch" +fi + +git fetch --prune || error 'Could not fetch remote changes' +git reset --hard "$upstream" || error "Could not reset inventory to $upstream" +git clean -fd || error 'Could not remove untracked inventory files' + +echo "Inventory reset to $upstream" +cleanup +trap - EXIT diff --git a/commit.py b/commit.py index 68a925e..8b23418 100644 --- a/commit.py +++ b/commit.py @@ -58,10 +58,26 @@ def process_diff(original_text, save_fn, filename): container_diff = process_diff(st.session_state['_container_plaintext'], save_containers, container_filename) confirm_discard = confirmation('Confirm discard') +confirm_clean_clone = confirmation('Confirm clean inventory reset') + def discard(): load_users(st.session_state) load_containers(st.session_state) +def clean_inventory(): + p = subprocess.run(['bash', 'clean_inventory.sh'], capture_output=True) + if p.returncode == 0: + discard() + st.rerun() + + out = p.stdout.decode().splitlines() + p.stderr.decode().splitlines() + st.write(':red[Could not reset the inventory repository.]') + st.divider() + st.html(f''' + {conv.produce_headers()} +
{''.join(map(convert, out))}
+ ''') + @st.dialog('Commit changes', width='large') def commit(): with st.form('commit-form'): @@ -75,16 +91,28 @@ def commit(): # Don't discard if script failed as that would import uncommitted changes and no longer show them # The changes which we saved will get reverted from the file upon rerun if p.returncode == 0: + st.session_state.pop('_commit_error_out', None) discard() st.rerun() else: - out = p.stdout.decode().splitlines() - st.write(':red[Could not pull with rebase. Remove any changes causing a merge conflict.]') - st.divider() - st.html(f''' - {conv.produce_headers()} -
{''.join(map(convert, out))}
- ''') + st.session_state['_commit_error_out'] = ( + p.stdout.decode().splitlines() + p.stderr.decode().splitlines() + ) + + if '_commit_error_out' in st.session_state: + out = st.session_state['_commit_error_out'] + st.write(':red[Could not save changes because the inventory repository changed in a conflicting way.]') + st.write('The application tried to update the local inventory before applying your changes. If the conflict cannot be resolved automatically, reset the local inventory to a clean copy from the remote and re-enter your changes.') + if st.button('Clean git clone / discard local inventory changes'): + confirm_clean_clone( + 'This will discard all local inventory changes and reset the inventory repository to the remote branch. Continue?', + clean_inventory, + ) + st.divider() + st.html(f''' + {conv.produce_headers()} +
{''.join(map(convert, out))}
+ ''') if st.session_state.diff_size > 0: with st.container(horizontal=True): diff --git a/commit.sh b/commit.sh old mode 100644 new mode 100755 index c88ab69..3749f28 --- a/commit.sh +++ b/commit.sh @@ -1,38 +1,69 @@ #! /usr/bin/env bash +set -u + lockfile=/opt/ccc-manager/commit.lock +inventory_dir=${CCC_INVENTORY_DIR:-/opt/ccc-inventory} + +function cleanup() { + rm -f "$lockfile" +} function error() { - echo $1 - rm -f $lockfile + echo "$1" + cleanup exit 1 } -if [[ -e $lockfile ]]; then +function reset_worktree() { + git rebase --abort >/dev/null 2>&1 || true + git merge --abort >/dev/null 2>&1 || true + git reset --hard HEAD >/dev/null 2>&1 || true +} + +if [[ -e "$lockfile" ]]; then error 'Could not acquire lock. Try again.' fi -touch $lockfile +touch "$lockfile" +trap cleanup EXIT patchfile=$1 -sed -e 's/\x1b\[[0-9;]*m//g' -i $patchfile +sed -e 's/\x1b\[[0-9;]*m//g' -i "$patchfile" -if [[ -z $2 ]]; then +if [[ -z ${2:-} ]]; then error 'Commit message not provided' fi -cd /opt/ccc-inventory/ +cd "$inventory_dir" || error "Inventory directory not found: $inventory_dir" + +upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true) +if [[ -n "$upstream" ]]; then + git fetch --prune || error 'Could not fetch remote changes' + git rebase "$upstream" || { + reset_worktree + error 'Could not synchronize with remote before applying changes' + } +else + git pull --rebase || error 'Could not pull with rebase' +fi + +git apply --3way "$patchfile" || { + git diff --diff-filter=U --color || true + reset_worktree + error 'Patch failed' +} -git apply $patchfile || error 'Patch failed' git add inventory/group_vars/ccc-cluster/{user-list,user-containers}.yml -git commit -m "$2" +git commit -m "$2" || error 'Commit failed' git pull --rebase STATUS=$? if [[ $STATUS -ne 0 ]]; then - git diff --diff-filter=U --color - git rebase --abort - git reset --hard HEAD~1 + git diff --diff-filter=U --color || true + git rebase --abort >/dev/null 2>&1 || true + git reset --hard HEAD~1 >/dev/null 2>&1 || true error 'Rebase not successful' fi -git push -rm $lockfile +git push || error 'Push failed' +cleanup +trap - EXIT