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

CyberShadow / aconfmgr / 633

18 Nov 2025 06:43PM UTC coverage: 90.037% (-2.6%) from 92.653%
633

push

github

web-flow
Merge 51c45cbbc into b3771975c

41 of 74 new or added lines in 8 files covered. (55.41%)

151 existing lines in 23 files now uncovered.

4582 of 5089 relevant lines covered (90.04%)

416.55 hits per line

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

87.69
/src/save.bash
1
# save.bash
2

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

5
function AconfSave() {
6
        local config_save_target=$config_dir/99-unsorted.sh
45✔
7
        local modified=n
45✔
8

9
        AconfCompile
45✔
10

11
        LogEnter 'Saving configuration...\n'
45✔
12

13
        #
14
        # Packages
15
        #
16

17
        LogEnter 'Examining packages...\n'
45✔
18

19
        # Unknown native packages (installed but not listed)
20

21
        local -a unknown_packages
45✔
22
        comm -13 <(PrintArray packages) <(PrintArray installed_packages) | mapfile -t unknown_packages
180✔
23

24
        if [[ ${#unknown_packages[@]} != 0 ]]
45✔
25
        then
26
                LogEnter 'Found %s unknown packages. Registering...\n' "$(Color G ${#unknown_packages[@]})"
10✔
27
                printf '\n\n# %s - Unknown packages\n\n\n' "$(date)" >> "$config_save_target"
10✔
28
                local package
5✔
29
                for package in "${unknown_packages[@]}"
11✔
30
                do
31
                        Log '%s...\r' "$(Color M "%q" "$package")"
22✔
32
                        local description
11✔
33
                        description="$(LC_ALL=C "$PACMAN" --query --info "$package" | grep '^Description' | cut -d ':' -f 2-)"
55✔
34
                        printf 'AddPackage %q #%s\n' "$package" "$description" >> "$config_save_target"
11✔
35
                done
36
                modified=y
5✔
37
                LogLeave
5✔
38
        fi
39

40
        # Missing native packages (listed but not installed on current system)
41

42
        local -a missing_packages
45✔
43
        comm -23 <(PrintArray packages) <(PrintArray installed_packages) | mapfile -t missing_packages
180✔
44

45
        if [[ ${#missing_packages[@]} != 0 ]]
45✔
46
        then
47
                LogEnter 'Found %s missing packages. Un-registering.\n' "$(Color G ${#missing_packages[@]})"
6✔
48
                printf '\n\n# %s - Missing packages\n\n\n' "$(date)" >> "$config_save_target"
6✔
49
                local package
3✔
50
                for package in "${missing_packages[@]}"
11✔
51
                do
52
                        printf 'RemovePackage %q\n' "$package" >> "$config_save_target"
11✔
53
                done
54
                modified=y
3✔
55
                LogLeave
3✔
56
        fi
57

58
        # Unknown foreign packages (installed but not listed)
59

60
        local -a unknown_foreign_packages
45✔
61
        comm -13 <(PrintArray foreign_packages) <(PrintArray installed_foreign_packages) | mapfile -t unknown_foreign_packages
180✔
62

63
        if [[ ${#unknown_foreign_packages[@]} != 0 ]]
45✔
64
        then
65
                LogEnter 'Found %s unknown foreign packages. Registering...\n' "$(Color G ${#unknown_foreign_packages[@]})"
6✔
66
                printf '\n\n# %s - Unknown foreign packages\n\n\n' "$(date)" >> "$config_save_target"
6✔
67
                local package
3✔
68
                for package in "${unknown_foreign_packages[@]}"
6✔
69
                do
70
                        Log '%s...\r' "$(Color M "%q" "$package")"
12✔
71
                        local description
6✔
72
                        description="$(LC_ALL=C "$PACMAN" --query --info "$package" | grep '^Description' | cut -d ':' -f 2-)"
30✔
73
                        printf 'AddPackage --foreign %q #%s\n' "$package" "$description" >> "$config_save_target"
6✔
74
                done
75
                modified=y
3✔
76
                LogLeave
3✔
77
        fi
78

79
        # Missing foreign packages (listed but not installed on current system)
80

81
        local -a missing_foreign_packages
45✔
82
        comm -23 <(PrintArray foreign_packages) <(PrintArray installed_foreign_packages) | mapfile -t missing_foreign_packages
180✔
83

84
        if [[ ${#missing_foreign_packages[@]} != 0 ]]
45✔
85
        then
86
                LogEnter 'Found %s missing foreign packages. Un-registering.\n' "$(Color G ${#missing_foreign_packages[@]})"
4✔
87
                printf '\n\n# %s - Missing foreign packages\n\n\n' "$(date)" >> "$config_save_target"
4✔
88
                local package
2✔
89
                for package in "${missing_foreign_packages[@]}"
6✔
90
                do
91
                        printf 'RemovePackage --foreign %q\n' "$package" >> "$config_save_target"
6✔
92
                done
93
                modified=y
2✔
94
                LogLeave
2✔
95
        fi
96

97
        LogLeave # Examining packages
45✔
98

99
        #
100
        # Emit files
101
        #
102

103
        LogEnter 'Registering files...\n'
45✔
104

105
        # Don't emit redundant CreateDir lines
106
        local -A skip_dirs
45✔
107
        local file
45✔
108
        ( Print0Array system_only_files ; Print0Array changed_files ) | \
90✔
109
                while read -r -d $'\0' file
74✔
110
                do
111
                        local path=${file%/*}
29✔
112
                        while [[ -n "$path" ]]
37✔
113
                        do
114
                                skip_dirs[$path]=y
8✔
115
                                path=${path%/*}
8✔
116
                        done
117
                done
118

119
        if [[ ${#config_only_files[@]} != 0 ]]
45✔
120
        then
121
                LogEnter 'Found %s extra files.\n' "$(Color G ${#config_only_files[@]})"
8✔
122
                printf '\n\n# %s - Extra files\n\n\n' "$(date)" >> "$config_save_target"
8✔
123
                local i
4✔
124
                for ((i=${#config_only_files[@]}-1; i>=0; i--))
20✔
125
                do
126
                        file=${config_only_files[$i]}
6✔
127
                        printf 'RemoveFile %q\n' "$file" >> "$config_save_target"
6✔
128
                done
129
                modified=y
4✔
130
                LogLeave
4✔
131
        fi
132

133
        if [[ ${#system_only_files[@]} != 0 || ${#changed_files[@]} != 0 ]]
75✔
134
        then
135
                LogEnter 'Found %s new and %s changed files.\n' "$(Color G ${#system_only_files[@]})" "$(Color G ${#changed_files[@]})"
48✔
136
                printf '\n\n# %s - New / changed files\n\n\n' "$(date)" >> "$config_save_target"
32✔
137
                local file
16✔
138
                ( Print0Array system_only_files ; Print0Array changed_files ) | \
32✔
139
                        while read -r -d $'\0' file
45✔
140
                        do
141
                                if [[ -n ${skip_dirs[$file]+x} ]]
29✔
142
                                then
143
                                        continue
2✔
144
                                fi
145

146
                                local dir
27✔
147
                                dir="$(dirname "$file")"
54✔
148
                                # shellcheck disable=SC2174
149
                                mkdir --mode=700 --parents "$config_dir"/files/"$dir"
27✔
150

151
                                unset func suffix heredoc_src prefix_to_expand # remove the previous values
27✔
152
                                local func suffix heredoc_src prefix_to_expand args=() props=()
81✔
153

154
                                local output_file="$output_dir"/files/"$file"
27✔
155
                                local system_file="$system_dir"/files/"$file"
27✔
156

157
                                local need_remove
27✔
158
                                if ! [[ -h "$output_file" || -e "$output_file" ]]
51✔
159
                                then
160
                                        need_remove=false # don't need RemoveFile if it doesn't exist
21✔
161
                                elif [[ -h "$output_file" || -h "$system_file" ]]
9✔
162
                                then
163
                                        need_remove=true # always need RemoveFile for symlinks
4✔
164
                                elif [[ ( -d "$output_file" && -d "$system_file" ) || ( -f "$output_file" && -f "$system_file" ) ]]
6✔
165
                                then
166
                                        need_remove=false # don't need RemoveFile if both are files or both are directories
1✔
167
                                else
168
                                        need_remove=true
1✔
169
                                fi
170

171
                                if $need_remove
27✔
172
                                then
173
                                        printf 'RemoveFile %q # Replacing %s with %s\n' "$file" \
25✔
174
                                                   "$(LC_ALL=C stat --format=%F "$output_file")" \
25✔
175
                                                   "$(LC_ALL=C stat --format=%F "$system_file")" \
25✔
176
                                                   >> "$config_save_target"
25✔
177
                                fi
178

179
                                if [[ -h "$system_file" ]]
27✔
180
                                then
181
                                        func=CreateLink
5✔
182
                                        args=("$file" "$(readlink "$system_file")")
10✔
183
                                        props=(owner group)
5✔
184
                                elif [[ -d "$system_file" ]]
22✔
185
                                then
186
                                        func=CreateDir
3✔
187
                                        args=("$file")
3✔
188
                                        props=(mode owner group)
3✔
189
                                else
190
                                        local size package
19✔
191
                                        size="$(LC_ALL=C stat --format=%s "$system_file")"
57✔
192
                                        package="$(pacman -Qoq "$file" 2>/dev/null || true)"
57✔
193
                                        if [[ $size -eq 0 ]]
19✔
194
                                        then
195
                                                func=CreateFile
2✔
196
                                                args=("$file")
2✔
197
                                                props=(mode owner group)
2✔
198
                                                suffix=' > /dev/null'
2✔
199

200
                                                if [[ -h "$output_file" || -e "$output_file" ]]
4✔
201
                                                then
202
                                                        printf 'RemoveFile %q\n' "$file" >> "$config_save_target"
1✔
203
                                                fi
204
                                        elif [[ $(wc -w <<<"$package") -le 1 ]] && AconfIsFileInlinable "$system_file"
51✔
205
                                        then
206
                                                AconfNeedProgram diff diffutils n
17✔
207
                                                if [[ -n "$package" ]]
17✔
208
                                                then
NEW
209
                                                        AconfNeedPackageFile "$package" >/dev/null
×
210
                                                        function GetOutputFileElsePackageFile() {
NEW
211
                                                                if [[ -f "$output_file" ]]
×
212
                                                                then
NEW
213
                                                                        cat "$output_file"
×
214
                                                                else
NEW
215
                                                                        AconfGetPackageOriginalFile "$package" "$file"
×
216
                                                                fi
217
                                                        }
218
                                                        {
219
                                                                # the output should contain a quoted command expansion
220
                                                                # shellcheck disable=SC2016
NEW
221
                                                                printf 'patch --no-backup-if-mismatch "$(GetPackageOriginalFile --no-clobber %s %q)" <<"EOF"\n' "$package" "$file"
×
NEW
222
                                                                 diff --unified=5 --label "output/$file" --label "system/$file" \
×
NEW
223
                                                                        <(GetOutputFileElsePackageFile) "$system_file" || true;
×
NEW
224
                                                                 printf 'EOF\n'
×
225
                                                                # we didn't have to worry about heredoc delimiter collisions because
226
                                                                # unified diff lines always begin with space, plus or minus
NEW
227
                                                        } >> "$config_save_target"
×
228
                                                elif [[ -f "$output_file" ]]
17✔
229
                                                then
230
                                                        {
231
                                                                # the output should contain a quoted command expansion
232
                                                                # shellcheck disable=SC2016
NEW
233
                                                                printf 'patch --no-backup-if-mismatch "$(CreateFile --no-clobber %q)" <<"EOF"\n' "$file"
×
NEW
234
                                                                diff --unified=5 --label "output/$file" --label "system/$file" \
×
NEW
235
                                                                        "$output_file" "$system_file" || true;
×
NEW
236
                                                                printf 'EOF\n'
×
237
                                                                # we didn't have to worry about heredoc delimiter collisions because
238
                                                                # unified diff lines always begin with space, plus or minus
NEW
239
                                                        } >> "$config_save_target"
×
240
                                                else
241
                                                        func=CreateFile
17✔
242
                                                        args=("$file")
17✔
243
                                                        props=(mode owner group)
17✔
244
                                                        prefix_to_expand='cat >'
17✔
245
                                                         if [[ -f "$output_file" ]]
17✔
246
                                                        then
NEW
247
                                                                heredoc_src="$output_file"
×
248
                                                        else
249
                                                                heredoc_src="$system_file"
17✔
250
                                                        fi
251
                                                        local contents
17✔
252
                                                        case "$(head -n2 "$heredoc_src" | wc -l)" in
85✔
253
                                                                0|1)
254
                                                                        contents=$(<"$heredoc_src")
17✔
255
                                                                        unset heredoc_src
17✔
256
                                                                        prefix_to_expand="printf -- '%s\n' ${contents@Q} >"
17✔
NEW
257
                                                                ;;&
×
258
                                                                0)
259
                                                                        prefix_to_expand="printf -- '%s' ${contents@Q} >"
17✔
260
                                                                ;;
261
                                                        esac
262
                                                fi
263
                                        else
UNCOV
264
                                                cp "$system_file" "$config_dir"/files/"$file"
×
UNCOV
265
                                                func=CopyFile
×
266
                                                args=("$file")
267
                                                props=(mode owner group)
268
                                        fi
269
                                fi
270

271
                                # Calculate the optional function parameters
272
                                local prop
27✔
273
                                for prop in "${props[@]}"
76✔
274
                                do
275
                                        local key="$file:$prop"
76✔
276
                                        if [[ -n "${system_file_props[$key]+x}" ]]
76✔
277
                                        then
278
                                                args+=("${system_file_props[$key]}")
14✔
279
                                                unset "output_file_props[\$key]"
14✔
280
                                                unset "system_file_props[\$key]"
14✔
281
                                        else
282
                                                args+=('')
62✔
283
                                        fi
284
                                done
285

286
                                # Trim redundant blank parameters
287
                                while [[ "${#args[*]}" -gt 0 && -z "${args[-1]}" ]]
178✔
288
                                do
289
                                        unset 'args[${#args[@]}-1]'
62✔
290
                                done
291

292
                                if [[ -v func ]]
27✔
293
                                then
294
                                        if [[ -v prefix_to_expand ]]
27✔
295
                                        then
296
                                                if [[ -v heredoc_src ]]
17✔
297
                                                then
NEW
298
                                                        local heredoc_delim
×
NEW
299
                                                        heredoc_delim="$(ChooseHeredocTerminator "$heredoc_src")"
×
300
                                                        # the output should contain a quoted command expansion
301
                                                        # shellcheck disable=SC2016
NEW
302
                                                        printf '%s "$(%s%s%s)" <<"%s"\n' "$prefix_to_expand" "$func" "$(printf ' %q' "${args[@]}")" "${suffix:-}" "$heredoc_delim"
×
303
                                                        # use sed to enforce trailing newline, via https://unix.stackexchange.com/a/31955
304
                                                        # shellcheck disable=SC1003
NEW
305
                                                        sed -e '$a\' "$heredoc_src"
×
NEW
306
                                                        printf '%s\n' "$heredoc_delim"
×
307
                                                else
308
                                                        # shellcheck disable=SC2016
309
                                                        printf '%s "$(%s%s%s)"\n' "$prefix_to_expand" "$func" "$(printf ' %q' "${args[@]}")" "${suffix:-}"
34✔
310
                                                fi
311
                                        else
312
                                                printf '%s%s%s\n' "$func" "$(printf ' %q' "${args[@]}")" "${suffix:-}"
20✔
NEW
313
                                        fi >> "$config_save_target"
×
314
                                fi
315
                        done
316
                modified=y
16✔
317
                LogLeave
16✔
318
        fi
319

320
        LogLeave # Emit files
45✔
321

322
        #
323
        # Emit remaining file properties
324
        #
325

326
        LogEnter 'Registering file properties...\n'
45✔
327

328
        AconfCompareFileProps # Update data after above unsets
45✔
329

330
        if [[ ${#system_only_file_props[@]} != 0 || ${#changed_file_props[@]} != 0 ]]
89✔
331
        then
332
                printf '\n\n# %s - New file properties\n\n\n' "$(date)" >> "$config_save_target"
2✔
333
                local key
1✔
334
                ( ( Print0Array system_only_file_props ; Print0Array changed_file_props ) | sort --zero-terminated ) | \
3✔
335
                        while read -r -d $'\0' key
4✔
336
                        do
337
                                printf 'SetFileProperty %q %q %q\n' "${key%:*}" "${key##*:}" "${system_file_props[$key]}" >> "$config_save_target"
3✔
338
                        done
339
                modified=y
1✔
340
        fi
341

342
        if [[ ${#config_only_file_props[@]} != 0 ]]
45✔
343
        then
344
                printf '\n\n# %s - Extra file properties\n\n\n' "$(date)" >> "$config_save_target"
6✔
345
                local key
3✔
346
                ( Print0Array config_only_file_props | sort --zero-terminated ) | \
6✔
347
                        while read -r -d $'\0' key
12✔
348
                        do
349
                                printf 'SetFileProperty %q %q %q\n' "${key%:*}" "${key##*:}" '' >> "$config_save_target"
9✔
350
                        done
351
                modified=y
3✔
352
        fi
353

354
        LogLeave # Registering file properties
45✔
355

356
        if [[ $modified == n ]]
45✔
357
        then
358
                LogLeave 'Done (%s).\n' "$(Color G "configuration unchanged")"
36✔
359
        else
360
                LogLeave 'Done (%s).\n' "$(Color Y "configuration changed")"
54✔
361
        fi
362
}
363

364
: # include in coverage
134✔
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

© 2026 Coveralls, Inc