• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberShadow / aconfmgr / 664

22 Dec 2025 03:28PM UTC coverage: 93.808% (+0.1%) from 93.708%
664

push

github

CyberShadow-Renovate
Update dependency yay to v20251214195934

1 of 1 new or added line in 1 file covered. (100.0%)

4 existing lines in 3 files now uncovered.

4712 of 5023 relevant lines covered (93.81%)

563.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.23
/src/apply.bash
1
# apply.bash
2

3
# This file contains the implementation of aconfmgr's 'apply' command.
4

5
function AconfApply() {
6
        local modified=n
97✔
7

8
        function PrintFileProperty() {
9
                local kind="$1"
137✔
10
                local value="$2"
137✔
11
                local file="$3"
137✔
12

13
                local value_text
137✔
14
                if [[ -z "$value" ]]
136✔
15
                then
16
                        local default_value
16✔
17
                        default_value="$(AconfDefaultFileProp "$file" "$kind")"
32✔
18
                        value_text="$(printf '%s (default value)' "$(Color G "%s" "$default_value")")"
48✔
19
                else
20
                        value_text="$(Color G "%s" "$value")"
242✔
21
                fi
22

23
                Log 'Setting %s of %s to %s\n'        \
411✔
24
                        "$(Color Y "%s" "$kind")"        \
411✔
25
                        "$(Color C "%q" "$file")"        \
411✔
26
                        "$value_text"
411✔
27
        }
28

29
        function ApplyFileProperty() {
30
                local kind="$1"
114✔
31
                local value="$2"
114✔
32
                local file="$3"
114✔
33

34
                PrintFileProperty "$kind" "$value" "$file"
114✔
35

36
                if [[ -z "$value" ]]
114✔
37
                then
38
                        value="$(AconfDefaultFileProp "$file" "$kind")"
16✔
39
                fi
40

41
                case "$kind" in
114✔
42
                        mode)
43
                                sudo chmod "$value" "$file"
34✔
44
                                ;;
45
                        owner)
46
                                sudo chown --no-dereference "$value" "$file"
40✔
47
                                ;;
48
                        group)
49
                                sudo chgrp --no-dereference "$value" "$file"
40✔
50
                                ;;
51
                        *)
52
                                Log 'Unknown property %s with value %s for file %s\n' \
×
53
                                        "$(Color Y "%q" "$kind" )" \
×
54
                                        "$(Color G "%q" "$value")" \
×
55
                                        "$(Color C "%q" "$file" )"
×
56
                                Exit 1
×
57
                                ;;
58
                esac
59
        }
60

61
        function ApplyFileProps() {
62
                local file="$1"
78✔
63
                local prop
78✔
64

65
                for prop in "${all_file_property_kinds[@]}"
248✔
66
                do
67
                        local key="$file:$prop"
248✔
68
                        if [[ -n "${output_file_props[$key]+x}" ]]
248✔
69
                        then
70
                                local value="${output_file_props[$key]}"
91✔
71
                                ApplyFileProperty "$prop" "$value" "$file"
91✔
72
                                unset "output_file_props[\$key]"
91✔
73
                                unset "system_file_props[\$key]"
91✔
74

75
                                printf '%s\t%s\t%q\n' "$prop" "$value" "$file" >> "$system_dir"/file-props.txt
91✔
76
                        fi
77
                done
78
        }
79

80
        function InstallFile() {
81
                local file="$1"
77✔
82
                local source="$output_dir"/files/"$file"
77✔
83

84
                # system
85

86
                local target="$file".aconfmgr-new
77✔
87
                sudo rm --force --dir "$target"
76✔
88

89
                # Is the target a directory?
90
                if sudo test ! -h "$file" -a -d "$file"
77✔
91
                then
92
                        # Is the source a directory?
93
                        if test ! -h "$source" -a -d "$source"
12✔
94
                        then
95
                                # Update the target's properties in-place.
96
                                target=$file
9✔
97
                        else
98
                                # Delete the existing directory.
99
                                # install/mv can't replace a directory with a non-directory.
100
                                sudo rm --force --dir "$file"
2✔
101
                        fi
102
                else
103
                        # Is the source a directory?
104
                        if test ! -h "$source" -a -d "$source"
57✔
105
                        then
106
                                # Delete the existing non-directory.
107
                                # install/mv can't replace a non-directory with a directory either.
108
                                sudo rm --force "$file"
10✔
109
                        fi
110
                fi
111

112
                sudo mkdir --parents "$(dirname "$target")"
152✔
113
                if [[ -h "$source" ]]
78✔
114
                then
115
                        sudo cp --no-dereference "$source" "$target"
12✔
116
                        sudo chown --no-dereference root:root "$target"
12✔
117
                elif [[ -d "$source" ]]
66✔
118
                then
119
                        sudo install -d \
23✔
120
                                 --mode="${output_file_props[$file:mode]:-755}" \
23✔
121
                                 --owner="${output_file_props[$file:owner]:-0}" \
23✔
122
                                 --group="${output_file_props[$file:group]:-0}" \
23✔
123
                                 "$target"
23✔
124
                else
125
                        sudo install \
43✔
126
                                 --mode="${output_file_props[$file:mode]:-$default_file_mode}" \
43✔
127
                                 --owner="${output_file_props[$file:owner]:-0}" \
43✔
128
                                 --group="${output_file_props[$file:group]:-0}" \
43✔
129
                                 "$source" "$target"
43✔
130
                fi
131

132
                if [[ "$target" != "$file" ]]
78✔
133
                then
134
                        sudo mv --force --no-target-directory "$target" "$file"
69✔
135
                fi
136

137
                # $system_dir
138

139
                local system_file="$system_dir"/files/"$file"
78✔
140
                if ! { test ! -h "$source" -a -d "$source" && test ! -h "$system_file" -a -d "$system_file" ; }
101✔
141
                then
142
                        rm --force --dir "$system_file"
74✔
143
                fi
144

145
                mkdir --parents "$(dirname "$system_file")"
156✔
146
                if [[ -h "$source" ]]
78✔
147
                then
148
                        cp --no-dereference "$source" "$system_file"
12✔
149
                elif [[ -d "$source" ]]
66✔
150
                then
151
                        mkdir --parents "$system_file"
23✔
152
                else
153
                        cp --no-dereference "$source" "$system_file"
43✔
154
                fi
155

156
                ApplyFileProps "$file"
78✔
157

158
                if [[ -h "$source" ]]
78✔
159
                then
160
                        # ApplyFileProps will apply and unset owner/group. For
161
                        # symlinks, we need to avoid attempting to restore the
162
                        # mode, so unset it here.
163
                        unset "system_file_props[\$file:mode]"
12✔
164
                        printf '%s\t%s\t%q\n' mode '' "$file" >> "$system_dir"/file-props.txt
12✔
165
                fi
166
        }
167

168
        AconfCompile
97✔
169

170
        LogEnter 'Applying configuration...\n'
96✔
171

172
        #
173
        # Priority files
174
        #
175

176
        LogEnter 'Installing priority files...\n'
96✔
177

178
        # shellcheck disable=SC2329  # Callback function invoked indirectly
179
        function Details_DiffFile() {
180
                if sudo test -d "$file"
29✔
181
                then
182
                        Log '%s (old) is a directory.\n' "$(Color C "%q" "$file")"
12✔
183
                elif test -d "$output_dir"/files/"$file"
24✔
184
                then
185
                        Log '%s (new) is a directory.\n' "$(Color C "%q" "$file")"
15✔
186
                else
187
                        AconfNeedProgram diff diffutils n
18✔
188
                        sudo "${diff_opts[@]}" --unified --no-dereference --report-identical-files "$file" "$output_dir"/files/"$file" || true
36✔
189
                fi
190
        }
191

192
        local file
95✔
193
        comm -12 --zero-terminated \
577✔
194
                 <(  Print0Array priority_files                                 | sort --zero-terminated ) \
×
195
                 <( (Print0Array config_only_files ; Print0Array changed_files) | sort --zero-terminated ) | \
×
196
                while read -r -d $'\0' file
109✔
197
                do
198
                        LogEnter 'Installing %s...\n' "$(Color C %q "$file")"
28✔
199

200
                        if sudo test -e "$file"
14✔
201
                        then
202
                                Confirm Details_DiffFile
7✔
203
                        else
204
                                Confirm ''
6✔
205
                        fi
206

207
                        InstallFile "$file"
14✔
208
                        LogLeave
14✔
209
                        modified=y
14✔
210
                done
211
        comm -23 --zero-terminated <(Print0Array config_only_files | sort --zero-terminated) <(Print0Array priority_files | sort --zero-terminated) | mapfile -d $'\0' config_only_files
582✔
212
        comm -23 --zero-terminated <(Print0Array changed_files     | sort --zero-terminated) <(Print0Array priority_files | sort --zero-terminated) | mapfile -d $'\0' changed_files
582✔
213

214
        LogLeave # Installing priority files
97✔
215

216
        if (LC_ALL=C "$PACMAN" 2>&1 || true) | grep --extended-regexp --only-matching 'database file for .+ does not exist'
388✔
217
        then
218
                Log 'The pacman local package database is internally inconsistent. Perform a full refresh and system upgrade now?\n'
1✔
219
                Confirm ''
1✔
220

221
                LogEnter 'Performing a full refresh and system upgrade...\n'
1✔
222
                sudo "$PACMAN" --sync --refresh --sysupgrade --noconfirm
1✔
223
                LogLeave
1✔
224
        fi
225

226
        #
227
        # Apply packages
228
        #
229

230
        LogEnter 'Configuring packages...\n'
97✔
231

232
        #        in                in                in-
233
        #        config        system        stalled foreign        action
234
        #        ----------------------------------------
235
        #        no                no                no                no                nothing
236
        #        no                no                no                yes                nothing
237
        #        no                no                yes                no                (prune)
238
        #        no                no                yes                yes                (prune)
239
        #        no                yes                no                no                impossible
240
        #        no                yes                no                yes                impossible
241
        #        no                yes                yes                no                unpin (and prune)
242
        #        no                yes                yes                yes                unpin (and prune)
243
        #        yes                no                no                no                install via pacman
244
        #        yes                no                no                yes                install via makepkg
245
        #        yes                no                yes                no                pin
246
        #        yes                no                yes                yes                pin
247
        #        yes                yes                no                no                impossible
248
        #        yes                yes                no                yes                impossible
249
        #        yes                yes                yes                no                nothing
250
        #        yes                yes                yes                yes                nothing
251

252
        # Unknown packages (native and foreign packages that are explicitly installed but not listed)
253
        local -a unknown_packages
97✔
254
        comm -13                                                                               \
679✔
255
                 <((PrintArray           packages ; PrintArray           foreign_packages) | sort) \
×
256
                 <((PrintArray installed_packages ; PrintArray installed_foreign_packages) | sort) \
×
257
                 | mapfile -t unknown_packages
97✔
258

259
        if [[ ${#unknown_packages[@]} != 0 ]]
97✔
260
        then
261
                LogEnter 'Unpinning %s unknown packages.\n' "$(Color G ${#unknown_packages[@]})"
18✔
262

263
                # shellcheck disable=SC2329  # Callback function invoked indirectly
264
                function Details() { Log 'Unpinning (setting install reason to '\''as dependency'\'') the following packages:%s\n' "$(Color M " %q" "${unknown_packages[@]}")" ; }
18✔
265
                Confirm Details
9✔
266

267
                Print0Array unknown_packages | sudo xargs -0 "$PACMAN" --database --asdeps
18✔
268

269
                modified=y
9✔
270
                LogLeave
9✔
271
        fi
272

273
        # Missing packages (native and foreign packages that are listed in the configuration, but not marked as explicitly installed)
274
        local -a missing_packages
97✔
275
        comm -23                                                                                                                                                           \
679✔
276
                 <((PrintArray           packages ; PrintArray           foreign_packages) | sort) \
×
277
                 <((PrintArray installed_packages ; PrintArray installed_foreign_packages) | sort) \
×
278
                 | mapfile -t missing_packages
97✔
279

280
        # Missing installed/unpinned packages (native and foreign packages that are implicitly installed,
281
        # and listed in the configuration, but not marked as explicitly installed)
282
        local -a missing_unpinned_packages
97✔
283
        comm -12 <(PrintArray missing_packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_unpinned_packages
509✔
284

285
        if [[ ${#missing_unpinned_packages[@]} != 0 ]]
97✔
286
        then
287
                LogEnter 'Pinning %s unknown packages.\n' "$(Color G ${#missing_unpinned_packages[@]})"
10✔
288

289
                # shellcheck disable=SC2329  # Callback function invoked indirectly
290
                function Details() { Log 'Pinning (setting install reason to '\''explicitly installed'\'') the following packages:%s\n' "$(Color M " %q" "${missing_unpinned_packages[@]}")" ; }
10✔
291
                Confirm Details
5✔
292

293
                Print0Array missing_unpinned_packages | sudo xargs -0 "$PACMAN" --database --asexplicit
10✔
294

295
                modified=y
5✔
296
                LogLeave
5✔
297
        fi
298

299
        # Orphan packages
300

301
        local -a files_in_deleted_packages=()
194✔
302

303
        if ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --native || true ) |
181✔
304
                   grep -qvFxf <(PrintArray ignore_packages        ) > /dev/null ||
194✔
305
           ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --foreign || true ) |
163✔
306
                   grep -qvFxf <(PrintArray ignore_foreign_packages) > /dev/null
164✔
307
        then
308
                LogEnter 'Pruning orphan packages...\n'
16✔
309

310
                # We have to loop, since pacman's dependency scanning doesn't seem to be recursive
311
                local iter=1
16✔
312
                while true
39✔
313
                do
314
                        LogEnter 'Iteration %s:\n' "$(Color G "$iter")"
78✔
315

316
                        LogEnter 'Querying orphan packages...\n'
39✔
317
                        local -a orphan_packages
39✔
318
                        {
319
                                ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --native || true ) |
52✔
320
                                        ( grep -vFxf <(PrintArray ignore_packages        ) || true )
95✔
321
                                ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --foreign || true ) |
69✔
322
                                        ( grep -vFxf <(PrintArray ignore_foreign_packages) || true )
110✔
323
                        } | mapfile -t orphan_packages
39✔
324
                        LogLeave
39✔
325

326
                        if [[ ${#orphan_packages[@]} != 0 ]]
39✔
327
                        then
328
                                LogEnter 'Pruning %s orphan packages.\n' "$(Color G ${#orphan_packages[@]})"
46✔
329

330
                                # shellcheck disable=SC2329  # Callback function invoked indirectly
331
                                function Details() { Log 'Removing the following orphan packages:%s\n' "$(Color M " %q" "${orphan_packages[@]}")" ; }
46✔
332
                                ParanoidConfirm Details
23✔
333

334
                                local -a deleted_files=()
46✔
335
                                "$PACMAN" --query --list --quiet "${orphan_packages[@]}" | sed 's#^\(.*\)/$#\1#' | mapfile -t deleted_files
69✔
336
                                files_in_deleted_packages+=("${deleted_files[@]}")
23✔
337

338
                                sudo "${pacman_opts[@]}" --remove "${orphan_packages[@]}"
23✔
339

340
                                LogLeave
23✔
341
                        fi
342

343
                        iter=$((iter+1))
39✔
344

345
                        LogLeave # Iteration
39✔
346

347
                        if [[ ${#orphan_packages[@]} == 0 ]]
39✔
348
                        then
349
                                break
16✔
350
                        fi
351
                done
352

353
                modified=y
16✔
354
                LogLeave # Removing orphan packages
16✔
355
        fi
356

357

358
        # Missing native packages (native packages that are listed in the configuration, but not installed)
359
        local -a missing_native_packages
97✔
360
        comm -23 <(PrintArray packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_native_packages
510✔
361

362
        if [[ ${#missing_native_packages[@]} != 0 ]]
97✔
363
        then
364
                LogEnter 'Installing %s missing native packages.\n' "$(Color G ${#missing_native_packages[@]})"
14✔
365

366
                # shellcheck disable=SC2329  # Callback function invoked indirectly
367
                function Details() { Log 'Installing the following native packages:%s\n' "$(Color M " %q" "${missing_native_packages[@]}")" ; }
14✔
368
                ParanoidConfirm Details
7✔
369

370
                AconfInstallNative "${missing_native_packages[@]}"
7✔
371

372
                modified=y
7✔
373
                LogLeave
7✔
374
        fi
375

376
        # Missing foreign packages (foreign packages that are listed in the configuration, but not installed)
377
        local -a missing_foreign_packages
97✔
378
        comm -23 <(PrintArray foreign_packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_foreign_packages
508✔
379

380
        if [[ ${#missing_foreign_packages[@]} != 0 ]]
97✔
381
        then
382
                LogEnter 'Installing %s missing foreign packages.\n' "$(Color G ${#missing_foreign_packages[@]})"
28✔
383

384
                # shellcheck disable=SC2329  # Callback function invoked indirectly
385
                function Details() { Log 'Installing the following foreign packages:%s\n' "$(Color M " %q" "${missing_foreign_packages[@]}")" ; }
28✔
386
                Confirm Details
14✔
387

388
                # If an AUR helper is present in the list of packages to be installed,
389
                # install it first, then use it to install the rest of the foreign packages.
390
                function InstallAurHelper() {
391
                        local package helper
14✔
392
                        for package in "${missing_foreign_packages[@]}"
15✔
393
                        do
394
                                for helper in "${aur_helpers[@]}"
100✔
395
                                do
396
                                        if [[ "$package" == "$helper" ]]
100✔
397
                                        then
398
                                                LogEnter 'Installing AUR helper %s...\n' "$(Color M %q "$helper")"
2✔
399
                                                ParanoidConfirm ''
1✔
400
                                                AconfInstallForeign "$package"
1✔
401
                                                aur_helper="$package"
1✔
402
                                                LogLeave
1✔
403
                                                return
1✔
404
                                        fi
405
                                done
406
                        done
407
                }
408
                if [[ $EUID != 0 ]]
14✔
409
                then
410
                        InstallAurHelper
14✔
411
                fi
412

413
                AconfInstallForeign "${missing_foreign_packages[@]}"
14✔
414

415
                modified=y
14✔
416
                LogLeave
14✔
417
        fi
418

419
        LogLeave # Configuring packages
97✔
420

421
        # Read file owners
422
        local -a modified_files_in_deleted_packages
97✔
423
        (
424
                cat "$tmp_dir"/output-files "$tmp_dir"/system-files
97✔
425
                local kind value file
97✔
426
                cat "$output_dir"/file-props.txt "$system_dir"/file-props.txt \
97✔
427
                        | \
×
428
                        while IFS=$'\t' read -r kind value file
896✔
429
                        do
430
                                eval "printf '%s\\0' $file" # Unescape
704✔
431
                        done
432
        ) \
×
433
                | sort --zero-terminated --unique \
97✔
434
                | comm --zero-terminated -12 <(Print0Array files_in_deleted_packages | sort --zero-terminated --unique) /dev/stdin \
291✔
435
                | tac -s $'\0' \
97✔
436
                | mapfile -t -d $'\0' modified_files_in_deleted_packages
97✔
437

438
        if [[ "${#modified_files_in_deleted_packages[@]}" -gt 0 ]]
96✔
439
        then
440
                LogEnter 'Detected %s modified files in pruned packages.\n' \
4✔
441
                                 "$(Color G %s "${#modified_files_in_deleted_packages[@]}")"
4✔
442

443
                LogEnter 'Updating system file data...\n'
2✔
444
                local file
1✔
445
                for file in "${modified_files_in_deleted_packages[@]}"
11✔
446
                do
447
                        local system_file="$system_dir"/files"$file"
11✔
448
                        if [[ -h "$system_file" || -f "$system_file" ]]
16✔
449
                        then
450
                                rm --force "$system_file"
2✔
451
                        elif [[ -d "$system_file" ]]
6✔
452
                        then
453
                                rmdir --ignore-fail-on-non-empty "$system_file"
5✔
454
                        elif [[ -e "$system_file" ]]
1✔
455
                        then
456
                                FatalError '%s exists, but is neither file or directory or link?\n' \
×
457
                                                   "$(Color C "%q" "$file")"
×
458
                        fi
459

460
                        local prop
11✔
461
                        for prop in "${all_file_property_kinds[@]}"
45✔
462
                        do
463
                                printf '%s\t%s\t%q\n' "$prop" '' "$file" >> "$system_dir"/file-props.txt
46✔
464
                        done
465
                done
466
                LogLeave
2✔
467

468
                LogEnter 'Updating owned file list...\n'
1✔
UNCOV
469
                ( "$PACMAN" --query --list --quiet || true ) | sed 's#\/$##' | sort --unique > "$tmp_dir"/owned-files
×
470
                LogLeave
2✔
471

472
                LogEnter 'Rescanning...\n'
2✔
473
                AconfAnalyzeFiles
2✔
474
                LogLeave
2✔
475
                LogLeave
2✔
476
        fi
477

478
        #
479
        # Copy files
480
        #
481

482
        LogEnter 'Configuring files...\n'
97✔
483

484
        if [[ ${#changed_files[@]} != 0 ]]
96✔
485
        then
486
                LogEnter 'Overwriting %s changed files.\n' "$(Color G ${#changed_files[@]})"
28✔
487

488
                # shellcheck disable=2059
489
                # shellcheck disable=SC2329  # Callback function invoked indirectly
490
                function Details() {
491
                        Log 'Overwriting the following changed files:\n'
14✔
492
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${changed_files[@]}"
47✔
493
                }
494
                Confirm Details
14✔
495

496
                for file in "${changed_files[@]}"
24✔
497
                do
498
                        LogEnter 'Overwriting %s...\n' "$(Color C "%q" "$file")"
48✔
499
                        ParanoidConfirm Details_DiffFile
23✔
500
                        InstallFile "$file"
23✔
501
                        LogLeave ''
24✔
502
                done
503

504
                modified=y
16✔
505
                LogLeave
16✔
506
        fi
507

508
        if [[ ${#config_only_files[@]} != 0 ]]
97✔
509
        then
510
                LogEnter 'Installing %s new files.\n' "$(Color G ${#config_only_files[@]})"
42✔
511

512
                # shellcheck disable=2059
513
                # shellcheck disable=SC2329  # Callback function invoked indirectly
514
                function Details() {
515
                        Log 'Installing the following new files:\n'
21✔
516
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${config_only_files[@]}"
63✔
517
                }
518
                Confirm Details
21✔
519

520
                for file in "${config_only_files[@]}"
40✔
521
                do
522
                        LogEnter 'Installing %s...\n' "$(Color C "%q" "$file")"
80✔
523
                        ParanoidConfirm ''
40✔
524
                        InstallFile "$file"
40✔
525
                        LogLeave ''
40✔
526
                done
527

528
                modified=y
21✔
529
                LogLeave
21✔
530
        fi
531

532
        local -a files_to_delete=()
194✔
533
        local -a files_to_restore=()
194✔
534

535
        if [[ ${#system_only_files[@]} != 0 ]]
97✔
536
        then
537
                LogEnter 'Processing system-only files...\n'
11✔
538

539
                # Delete unknown stray files (files not present in config and belonging to no package)
540

541
                LogEnter 'Filtering system-only stray files...\n'
10✔
542
                local system_only_stray_files=0
10✔
543
                tr '\n' '\0' < "$tmp_dir"/owned-files > "$tmp_dir"/owned-files-0
10✔
544
                comm -13 --zero-terminated "$tmp_dir"/owned-files-0 <(Print0Array system_only_files) | \
22✔
545
                        while read -r -d $'\0' file
29✔
546
                        do
547
                                files_to_delete+=("$file")
18✔
548
                                system_only_stray_files=$((system_only_stray_files+1))
18✔
549
                        done
550
                LogLeave 'Done (%s system-only stray files).\n' "$(Color G %s $system_only_stray_files)"
22✔
551

552
                # Restore unknown owned files (files not present in config and belonging to a package)
553

554
                LogEnter 'Filtering system-only owned files...\n'
10✔
555
                local system_only_owned_files=0
10✔
556
                comm -12 --zero-terminated "$tmp_dir"/owned-files-0 <(Print0Array system_only_files) | \
22✔
557
                        while read -r -d $'\0' file
23✔
558
                        do
559
                                if [[ "${output_file_props[$file:deleted]:-}" == y ]]
12✔
560
                                then
561
                                        continue # Don't restore files that the user wants deleted
2✔
562
                                fi
563

564
                                files_to_restore+=("$file")
10✔
565
                                system_only_owned_files=$((system_only_owned_files+1))
10✔
566
                        done
567
                LogLeave 'Done (%s system-only owned files).\n' "$(Color G %s $system_only_owned_files)"
22✔
568

569
                LogLeave # Processing system-only files
11✔
570
        fi
571

572
        LogEnter 'Processing deleted files...\n'
97✔
573

574
        if [[ ${#config_only_file_props[@]} != 0 ]]
97✔
575
        then
576
                local key
12✔
577
                for key in "${config_only_file_props[@]}"
45✔
578
                do
579
                        if [[ "$key" == *:deleted ]]
45✔
580
                        then
581
                                local file="${key%:*}"
11✔
582
                                files_to_delete+=("$file")
11✔
583
                                unset "output_file_props[\$key]"
10✔
584
                        fi
585
                done
586
        fi
587

588
        if [[ ${#system_only_file_props[@]} != 0 ]]
96✔
589
        then
590
                local key
5✔
591
                for key in "${system_only_file_props[@]}"
15✔
592
                do
593
                        if [[ "$key" == *:deleted ]]
15✔
594
                        then
595
                                local file="${key%:*}"
4✔
596

597
                                if [[ -h "$output_dir"/files/"$file" || -e "$output_dir"/files/"$file" ]]
7✔
598
                                then
599
                                        # If we are going to replace a deleted file with
600
                                        # one from the configuration, do not attempt to
601
                                        # restore it.
602
                                        :
2✔
603
                                else
604
                                        files_to_restore+=("$file")
2✔
605
                                fi
606

607
                                unset "system_file_props[\$key]"
4✔
608
                        fi
609
                done
610
        fi
611

612
        LogLeave # Processing deleted files
96✔
613

614
        if [[ ${#files_to_delete[@]} != 0 ]]
94✔
615
        then
616
                LogEnter 'Deleting %s files.\n' "$(Color G ${#files_to_delete[@]})"
24✔
617
                printf '%s\0' "${files_to_delete[@]}" | sort --zero-terminated | mapfile -d $'\0' files_to_delete
36✔
618

619
                # shellcheck disable=2059
620
                # shellcheck disable=SC2329  # Callback function invoked indirectly
621
                function Details() {
622
                        Log 'Deleting the following files:\n'
12✔
623
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${files_to_delete[@]}"
36✔
624
                }
625
                Confirm Details
12✔
626

627
                local -A parents=()
24✔
628

629
                # Iterate backwards, so that inner files/directories are
630
                # deleted before their parent ones.
631
                local i
12✔
632
                for (( i=${#files_to_delete[@]}-1 ; i >= 0 ; i-- ))
84✔
633
                do
634
                        local file="${files_to_delete[$i]}"
30✔
635

636
                        if [[ -n "${parents[$file]+x}" && -n "$(sudo find "$file" -maxdepth 0 -type d -not -empty 2>/dev/null)" ]]
58✔
637
                        then
638
                                # Ignoring paths under a directory can cause us to
639
                                # want to remove a directory which will in fact not be
640
                                # empty, and actually contain ignored files. So, skip
641
                                # deleting empty directories which are parents of
642
                                # previously-deleted objects.
643
                                LogEnter 'Skipping non-empty directory %s.\n' "$(Color C "%q" "$file")"
4✔
644
                        else
645
                                LogEnter 'Deleting %s...\n' "$(Color C "%q" "$file")"
56✔
646
                                ParanoidConfirm ''
27✔
647
                                sudo rm --dir "$file"
27✔
648
                        fi
649

650
                        local prop
30✔
651
                        for prop in "${all_file_property_kinds[@]}"
40✔
652
                        do
653
                                local key="$file:$prop"
40✔
654
                                unset "system_file_props[\$key]"
40✔
655
                        done
656

657
                        parents["$(dirname "$file")"]=y
60✔
658

659
                        LogLeave ''
30✔
660
                done
661

662
                modified=y
12✔
663
                LogLeave
12✔
664
        fi
665

666
        if [[ ${#files_to_restore[@]} != 0 ]]
95✔
667
        then
668
                LogEnter 'Restoring %s files.\n' "$(Color G ${#files_to_restore[@]})"
12✔
669
                printf '%s\0' "${files_to_restore[@]}" | sort --zero-terminated | mapfile -d $'\0' files_to_restore
18✔
670

671
                # shellcheck disable=2059
672
                # shellcheck disable=SC2329  # Callback function invoked indirectly
673
                function Details() {
674
                        Log 'Restoring the following files:\n'
6✔
675
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${files_to_restore[@]}"
18✔
676
                }
677
                Confirm Details
6✔
678

679
                # Read file owners
680
                local -A file_owners
6✔
681
                local file
6✔
682
                while read -r -d $'\0' file
196✔
683
                do
684
                        local package
191✔
685
                        read -r -d $'\0' package
190✔
686
                        file_owners[$file]=$package
191✔
687
                done < "$tmp_dir"/file-owners
×
688

689
                for file in "${files_to_restore[@]}"
10✔
690
                do
691
                        local package
10✔
692

693
                        if [[ -n "${file_owners[$file]+x}" ]]
10✔
694
                        then
695
                                package=${file_owners[$file]}
9✔
696
                        else
697
                                package="$( ("$PACMAN" --query --owns --quiet "$file" || true) | head -n 1)"
3✔
698

699
                                if [[ -z "$package" ]]
1✔
700
                                then
701
                                        Log 'Can'\''t find package owning file %s\n' "$(Color C "%q" "$file")"
×
702
                                        Exit 1
×
703
                                fi
704
                        fi
705

706
                        LogEnter 'Restoring %s file %s...\n' "$(Color M "%q" "$package")" "$(Color C "%q" "$file")"
34✔
707
                        # shellcheck disable=SC2329  # Callback function invoked indirectly
708
                        function Details() {
709
                                AconfNeedProgram diff diffutils n
2✔
710
                                AconfGetPackageOriginalFile "$package" "$file" | ( "${diff_opts[@]}" --unified <(SuperCat "$file") - || true )
8✔
711
                        }
712
                        if sudo test -f "$file"
12✔
713
                        then
714
                                ParanoidConfirm Details
2✔
715
                        else
716
                                ParanoidConfirm ''
10✔
717
                        fi
718

719
                        AconfRestoreFile "$package" "$file"
11✔
720

721
                        # The file was restored with all of its original properties.
722
                        local prop
12✔
723
                        for prop in owner group mode
36✔
724
                        do
725
                                unset "system_file_props[\$file:\$prop]"
36✔
726
                        done
727

728
                        LogLeave ''
12✔
729
                done
730

731
                modified=y
6✔
732
                LogLeave
6✔
733
        fi
734

735
        #
736
        # Apply remaining file properties
737
        #
738

739
        LogEnter 'Configuring file properties...\n'
95✔
740

741
        AconfCompareFileProps # Update data after ApplyFileProps' unsets
94✔
742

743
        if [[ ${#config_only_file_props[@]} != 0 || ${#changed_file_props[@]} != 0 || ${#system_only_file_props[@]} != 0 ]]
268✔
744
        then
745
                LogEnter 'Found %s new, %s changed, and %s extra files properties.\n'        \
24✔
746
                                 "$(Color G ${#config_only_file_props[@]})"                                                \
24✔
747
                                 "$(Color G ${#changed_file_props[@]})"                                                 \
24✔
748
                                 "$(Color G ${#system_only_file_props[@]})"
24✔
749

24✔
750
                # shellcheck disable=SC2329  # Callback function invoked indirectly
751
                function LogFileProps() {
752
                        local verb="$1"
18✔
753
                        local first=y
18✔
754
                        local key
18✔
755

756
                        while read -r -d $'\0' key
41✔
757
                        do
758
                                if [[ $first == y ]]
23✔
759
                                then
760
                                        LogEnter '%s the following file properties:\n' "$verb"
7✔
761
                                        first=n
7✔
762
                                fi
763

764
                                local kind="${key##*:}"
23✔
765
                                local file="${key%:*}"
23✔
766
                                local value="${output_file_props[$key]:-}"
23✔
767
                                PrintFileProperty "$kind" "$value" "$file"
23✔
768
                        done
769

770
                        if [[ $first == n ]]
18✔
771
                        then
772
                                LogLeave ''
7✔
773
                        fi
774
                }
775

776
                # shellcheck disable=2059
777
                # shellcheck disable=SC2329  # Callback function invoked indirectly
778
                function Details() {
779
                        Print0Array config_only_file_props | LogFileProps "Setting"
12✔
780
                        Print0Array changed_file_props     | LogFileProps "Updating"
12✔
781
                        Print0Array system_only_file_props | LogFileProps "Clearing"
12✔
782
                }
783
                Confirm Details
6✔
784

785
                local key
6✔
786
                ( Print0Array config_only_file_props ; Print0Array changed_file_props ; Print0Array system_only_file_props ) | \
18✔
787
                        while read -r -d $'\0' key
29✔
788
                        do
789
                                local kind="${key##*:}"
23✔
790
                                local file="${key%:*}"
23✔
791
                                local value="${output_file_props[$key]:-}"
23✔
792

793
                                ApplyFileProperty "$kind" "$value" "$file"
23✔
794
                        done
795

796
                modified=y
6✔
797
                LogLeave
6✔
798
        fi
799

800
        LogLeave # Configuring file properties
92✔
801

802
        LogLeave # Configuring files
92✔
803

804
        if [[ $modified == n ]]
92✔
805
        then
806
                LogLeave 'Done (%s).\n' "$(Color G "system state unchanged")"
30✔
807
        else
808
                LogLeave 'Done (%s).\n' "$(Color Y "system state changed")"
158✔
809
        fi
810
}
811

812
: # include in coverage
180✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc