diff --git a/cli/bash/commands/basectl/tests/update-profile.bats b/cli/bash/commands/basectl/tests/update-profile.bats index 82b9937..978a3fc 100644 --- a/cli/bash/commands/basectl/tests/update-profile.bats +++ b/cli/bash/commands/basectl/tests/update-profile.bats @@ -43,6 +43,10 @@ load ./setup_helpers.bash run_base_command update-profile [ "$status" -eq 0 ] + [[ "$output" != *"Updating '$TEST_HOME/.bash_profile'"* ]] + [[ "$output" != *"Updating '$TEST_HOME/.bashrc'"* ]] + [[ "$output" != *"Updating '$TEST_HOME/.zprofile'"* ]] + [[ "$output" != *"Updating '$TEST_HOME/.zshrc'"* ]] [ "$(grep -c '# --- BEGIN base bashrc MANAGED SECTION - DO NOT EDIT ---' "$TEST_HOME/.bashrc")" -eq 1 ] [ "$(grep -c '# --- END base bashrc MANAGED SECTION - DO NOT EDIT ---' "$TEST_HOME/.bashrc")" -eq 1 ] [[ "$(cat "$TEST_HOME/.bashrc")" == *"user line before"* ]] diff --git a/lib/bash/file/lib_file.sh b/lib/bash/file/lib_file.sh index 490eada..06974d1 100644 --- a/lib/bash/file/lib_file.sh +++ b/lib/bash/file/lib_file.sh @@ -80,7 +80,11 @@ update_file_section() { return 1 fi - log_info "Updating '$target_file'" + local section_exists=false + if ((beginning_marker_count > 0)); then + section_exists=true + fi + local new_content_string="" if [[ "$remove_section" == false ]]; then if [[ ${#new_content_array[@]} -gt 0 ]]; then @@ -90,29 +94,73 @@ update_file_section() { fi fi - local temp_file new_content_file - temp_file=$(mktemp "${target_file}.XXXXXX") - if [[ ! -f "$temp_file" ]]; then - log_error "Failed to create temporary file for '$target_file'." - return 1 - fi - - new_content_file=$(mktemp "${target_file}.new.XXXXXX") - if [[ ! -f "$new_content_file" ]]; then - log_error "Failed to create temporary content file for '$target_file'." - rm -f "$temp_file" - return 1 + if [[ "$section_exists" == false && "$remove_section" == true ]]; then + log_debug "Section not present in '$target_file'; nothing to remove." + return 0 fi + local current_content_file="" new_content_file="" temp_file if [[ "$remove_section" == false ]]; then + new_content_file=$(mktemp "${TMPDIR:-/tmp}/base-file-section-new.XXXXXX") + if [[ ! -f "$new_content_file" ]]; then + log_error "Failed to create temporary content file for '$target_file'." + return 1 + fi + if ! printf '%s' "$new_content_string" > "$new_content_file"; then log_error "Failed to write replacement content for '$target_file'." - rm -f "$temp_file" "$new_content_file" + rm -f "$new_content_file" + return 1 + fi + fi + + if [[ "$section_exists" == true && "$remove_section" == false ]]; then + current_content_file=$(mktemp "${TMPDIR:-/tmp}/base-file-section-current.XXXXXX") + if [[ ! -f "$current_content_file" ]]; then + log_error "Failed to create temporary current content file for '$target_file'." + rm -f "$new_content_file" + return 1 + fi + + if ! awk -v START_M="$beginning_marker" -v END_M="$end_marker" ' + BEGIN { + in_section = 0 + processed = 0 + } + $0 == START_M && processed == 0 { + in_section = 1 + next + } + $0 == END_M && in_section == 1 { + processed = 1 + exit + } + in_section == 1 { + print $0 + } + ' "$target_file" > "$current_content_file"; then + log_error "Failed to read existing section in '$target_file'." + rm -f "$current_content_file" "$new_content_file" return 1 fi + + if cmp -s "$current_content_file" "$new_content_file"; then + log_debug "Section already up to date in '$target_file'." + rm -f "$current_content_file" "$new_content_file" + return 0 + fi + rm -f "$current_content_file" fi - if grep -qF -- "$beginning_marker" "$target_file" && grep -qF -- "$end_marker" "$target_file"; then + log_info "Updating '$target_file'" + temp_file=$(mktemp "${target_file}.XXXXXX") + if [[ ! -f "$temp_file" ]]; then + log_error "Failed to create temporary file for '$target_file'." + rm -f "$new_content_file" + return 1 + fi + + if [[ "$section_exists" == true ]]; then if [[ "$remove_section" == true ]]; then if awk -v START_M="$beginning_marker" -v END_M="$end_marker" ' BEGIN { in_section = 0 } @@ -161,42 +209,37 @@ update_file_section() { return 1 else # Markers not found in the file - if [[ "$remove_section" == true ]]; then + if ! cp "$target_file" "$temp_file"; then + log_error "Failed to copy '$target_file' to '$temp_file'." rm -f "$temp_file" "$new_content_file" - return 0 - else - if ! cp "$target_file" "$temp_file"; then - log_error "Failed to copy '$target_file' to '$temp_file'." - rm -f "$temp_file" "$new_content_file" - return 1 - fi - - if [[ $(tail -c 1 "$temp_file" 2>/dev/null | wc -l) -eq 0 ]]; then - if ! printf '\n' >> "$temp_file"; then - log_error "Failed to add trailing newline to '$temp_file'." - rm -f "$temp_file" "$new_content_file" - return 1 - fi - fi + return 1 + fi - if ! { - printf '%s\n' "$beginning_marker" - printf '%s' "$new_content_string" - printf '%s\n' "$end_marker" - } >> "$temp_file"; then - log_error "Failed to add new section to '$target_file'." + if [[ $(tail -c 1 "$temp_file" 2>/dev/null | wc -l) -eq 0 ]]; then + if ! printf '\n' >> "$temp_file"; then + log_error "Failed to add trailing newline to '$temp_file'." rm -f "$temp_file" "$new_content_file" return 1 fi + fi - if ! mv -f "$temp_file" "$target_file"; then - log_error "Failed to replace '$target_file' with '$temp_file'." - rm -f "$temp_file" "$new_content_file" - return 1 - fi + if ! { + printf '%s\n' "$beginning_marker" + printf '%s' "$new_content_string" + printf '%s\n' "$end_marker" + } >> "$temp_file"; then + log_error "Failed to add new section to '$target_file'." + rm -f "$temp_file" "$new_content_file" + return 1 + fi - rm -f "$new_content_file" - return 0 + if ! mv -f "$temp_file" "$target_file"; then + log_error "Failed to replace '$target_file' with '$temp_file'." + rm -f "$temp_file" "$new_content_file" + return 1 fi + + rm -f "$new_content_file" + return 0 fi } diff --git a/lib/bash/file/tests/lib_file.bats b/lib/bash/file/tests/lib_file.bats index 3098225..9195f59 100644 --- a/lib/bash/file/tests/lib_file.bats +++ b/lib/bash/file/tests/lib_file.bats @@ -8,6 +8,14 @@ setup() { source "$BASE_BASH_DIR/file/lib_file.sh" } +file_inode() { + if stat -c '%i' "$1" >/dev/null 2>&1; then + stat -c '%i' "$1" + else + stat -f '%i' "$1" + fi +} + @test "update_file_section appends a new marked block when markers are absent" { local target="$TEST_TMPDIR/config.txt" printf 'line-one' > "$target" @@ -62,6 +70,33 @@ EOF [ "$(cat "$target")" = $'before\n# BEGIN\nfirst\nsecond\nthird\n# END\nafter' ] } +@test "update_file_section skips unchanged existing section" { + local before_inode + local target="$TEST_TMPDIR/config.txt" + cat <<'EOF' > "$target" +before +# BEGIN +same +content +# END +after +EOF + before_inode="$(file_inode "$target")" + + bats_run update_file_section "$target" "# BEGIN" "# END" "same" "content" + + [ "$status" -eq 0 ] + [[ "$output" != *"Updating '$target'"* ]] + [ "$(file_inode "$target")" = "$before_inode" ] + [ "$(cat "$target")" = $'before\n# BEGIN\nsame\ncontent\n# END\nafter' ] + + set_log_level DEBUG + bats_run update_file_section "$target" "# BEGIN" "# END" "same" "content" + + [ "$status" -eq 0 ] + [[ "$output" == *"Section already up to date in '$target'."* ]] +} + @test "update_file_section does not export replacement content to awk" { local awk_log="$TEST_TMPDIR/awk-env.log" local target="$TEST_TMPDIR/config.txt"