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