git-wad (21964B)
1 #!/bin/sh 2 3 # Copyright (C) 2023-2025 |Méso|Star> (contact@meso-star.com) 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 set -e 19 20 die() 21 { 22 rm -rf "${git_wad_tmpdir}" # cleanup temporary files 23 exit "${1:-1}" # return status code (default is 1) 24 } 25 26 # Configure signal processing 27 trap 'die $?' EXIT 28 29 # Check that git-wad does not run from a .git directory 30 is_inside_git_dir="$(git rev-parse --is-inside-git-dir)" 31 if [ "${is_inside_git_dir}" = "true" ]; then 32 >&2 printf 'git-wad must be run in a work tree\n' 33 exit 1 34 fi 35 36 # Force default locale when using sed and tr, i.e. treat all characters 37 # as individual bytes. Otherwise, some implementations return multi-byte 38 # encoding errors. 39 alias sed__='LC_CTYPE=C sed' 40 alias tr__='LC_CTYPE=C tr' 41 42 # Check checksum command support 43 if ! command -v sha256sum 1> /dev/null 2>&1 \ 44 && ! command -v shasum 1> /dev/null 2>&1 \ 45 && ! command -v sha256 1> /dev/null 2>&1; then 46 >&2 printf 'No tool to process SHA256 checksum\n' 47 exit 1 48 fi 49 50 # Check C compiler support 51 if command -v c99 1> /dev/null 2>&1; then 52 CC="c99" 53 elif command -v cc 1> /dev/null 2>&1; then 54 CC="cc" 55 else 56 >&2 printf 'No C compiler\n' 57 exit 1 58 fi 59 60 # shellcheck disable=SC2310 61 working_tree="$(git rev-parse --show-toplevel)" 62 git_wad_tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/git_wad_XXXXXX")" 63 64 # Path toward the next_timestamp command 65 next_timestamp="${git_wad_tmpdir}/next_timestamp" 66 67 # Load local config file 68 config_file="${working_tree}/.git_wad.cfg" 69 if [ -f "${config_file}" ]; then 70 # shellcheck disable=SC1090 71 . "${config_file}" 72 fi 73 74 GIT_WAD_HEADER="#\$# git-wad" 75 GIT_WAD_OBJDIR="${GIT_WAD_OBJDIR:-${working_tree}/.git/wad}" 76 GIT_WAD_VERBOSE="${GIT_WAD_VERBOSE:-0}" 77 78 if [ -z "${GIT_WAD_REMOTE_FETCH}" ] \ 79 && git remote | grep -qe "^origin$"; then 80 GIT_WAD_REMOTE_FETCH="$(git config remote.origin.url || true)" 81 fi 82 if [ -z "${GIT_WAD_REMOTE_PUSH}" ] \ 83 && git remote | grep -qe "^origin$"; then 84 GIT_WAD_REMOTE_PUSH="$(git config remote.origin.pushurl || true)" 85 if [ -z "${GIT_WAD_REMOTE_PUSH}" ]; then 86 GIT_WAD_REMOTE_PUSH="${GIT_WAD_REMOTE_FETCH}" 87 fi 88 fi 89 90 ######################################################################## 91 # Helper functions 92 ######################################################################## 93 synopsis() 94 { 95 >&2 printf "usage: git-wad checkout\n" 96 >&2 printf " git-wad fetch [-1a]\n" 97 >&2 printf " git-wad fsck [-r]\n" 98 >&2 printf " git-wad init\n" 99 >&2 printf " git-wad prune [-1a]\n" 100 >&2 printf " git-wad pull [-1a]\n" 101 >&2 printf " git-wad push [-1a\n" 102 >&2 printf " git-wad status [-1a]\n" 103 } 104 105 is_init() 106 { 107 git config --get filter.wad.clean > /dev/null \ 108 && git config --get filter.wad.smudge > /dev/null 109 } 110 111 sizeof_header() 112 { 113 printf "%s" "${GIT_WAD_HEADER}" | wc -c 114 } 115 116 encode() # digest, size 117 { 118 printf "%s %s %d" "${GIT_WAD_HEADER}" "$1" "$2" 119 } 120 121 # List hashes of WAD file from the current HEAD 122 wad_hashes() 123 { 124 # Regular expression of a WAD 125 wad_re="${GIT_WAD_HEADER} [0-9a-z]\{64\} [0-9]\{1,\}" 126 127 # The following command line can be translated as follows: 128 # Print the IDs of all objects in HEAD 129 # | Get their hash 130 # | Print object information of these hashes 131 # | Keep only the hash of objects that are blobs 132 # | Print blob content preceeded by its hash 133 # | Print the blob hash if the following line indicates a WAD 134 git rev-list --objects HEAD -1 \ 135 | cut -d' ' -f1 \ 136 | git cat-file --batch-check \ 137 | sed__ -n 's/\(^[a-z0-9]\{40\}\) blob [0-9]\{1,\}$/\1/p' \ 138 | git cat-file --batch='%(objectname)' \ 139 | sed__ -n "/^[0-9a-z]\{40\}$/{N;s/^\([0-9a-z]\{40\}\)\n${wad_re}$/\1/p;}" 140 } 141 142 # List paths of WAD file from the current commit 143 wad_paths() 144 { 145 # Store the hash of WAD file 146 hashes="${git_wad_tmpdir}/hashes" 147 wad_hashes | sort -b > "${hashes}" 148 149 # Lists the current tree. Ensure verbatim filename (-z option) 150 # | Ensure one entry per line (see the output section of git-ls-tree(1)) 151 # | Print only the hash and path of listed files 152 # | Sort the result in ascending order 153 # | Print path for WAD files only 154 git ls-tree -zr HEAD "${working_tree}" \ 155 | tr__ '\0' '\n' \ 156 | cut -d' ' -f3- \ 157 | sort -b \ 158 | join -t' ' -o 2.2 "${hashes}" - 159 } 160 161 # List WAD objects from the current working tree 162 wad_objects() # [-1a] 163 { 164 rev="HEAD" # Revision 165 166 OPTIND=1 167 while getopts ":a1" opt; do 168 case "${opt}" in 169 1) rev="HEAD -1" ;; # Last commit only 170 a) rev="--all" ;; 171 *) 172 >&2 printf "Invalid option -- %s\n" "${OPTARG}" 173 return 1 174 ;; 175 esac 176 done 177 178 # The following command line can be translated as follows: 179 # Print the IDs of all objects in "${rev}" (i.e. HEAD or --all) 180 # | Get their hash 181 # | Print object information of these hashes 182 # | Keep only the hash of objects that are blobs 183 # | Print information _and_ contents of these hashes 184 # | Finally print digest of WAD object 185 # shellcheck disable=SC2086 # ${rev} can have several arguments 186 git rev-list --objects ${rev} \ 187 | cut -d' ' -f1 \ 188 | git cat-file --batch-check \ 189 | sed__ -n 's/\(^[a-z0-9]\{40\}\) blob [0-9]\{1,\}$/\1/p' \ 190 | git cat-file --batch \ 191 | sed__ -n "s/^${GIT_WAD_HEADER} \([0-9a-z]\{64\}\) [0-9]\{1,\}$/\1/p" 192 } 193 194 # List WAD objects staged for the next commit 195 wad_objects_staged() 196 { 197 git diff --cached \ 198 | sed__ -n "s/^+${GIT_WAD_HEADER} \([0-9a-z]\{64\}\) [0-9]\{1,\}$/\1/p" 199 } 200 201 # List all stored WAD objects of the working tree. These may be WAD 202 # objects from the current HEAD, WAD objects to be cleaned up, or dummy 203 # files. 204 all_objects() 205 { 206 find "${GIT_WAD_OBJDIR}" ! -path "${GIT_WAD_OBJDIR}" -prune -type f \ 207 | grep -e "${GIT_WAD_OBJDIR}/[0-9a-z]\{64\}" \ 208 | xargs -I {} basename {} 209 } 210 211 # List of all locally stored WAD objects to be pruned, i.e. not 212 # referenced in the current work tree. 213 unreferenced_objects() # [-1a] 214 { 215 # Lists all locally stored WAD objects in tmpfile 216 local_wads="${git_wad_tmpdir}/local_wads" 217 all_objects | sort > "${local_wads}" 218 219 # List WAD objects in the current working tree or to commit 220 wads="${git_wad_tmpdir}/wads" 221 # shellcheck disable=SC2310 222 { wad_objects "$@" && wad_objects_staged; } > "${wads}" 223 224 # The following command line is translated as follows: 225 # Sort WAD objects in ascending order 226 # | Subtract them from the list of locally stored WAD objects 227 sort "${wads}" | comm -23 "${local_wads}" - 228 } 229 230 objects_to_push() # [-1a] 231 { 232 wads="${git_wad_tmpdir}/wad_objects" 233 # shellcheck disable=SC2310 234 wad_objects "$@" > "${wads}" 235 236 xargs -I {} sh -c \ 237 "if [ -f \"${GIT_WAD_OBJDIR}/\$1\" ]; then echo \"\$1\"; fi" -- {} \ 238 < "${wads}" 239 } 240 241 objects_to_fetch() # [-1a] 242 { 243 wads="${git_wad_tmpdir}/wad_objects" 244 # shellcheck disable=SC2310 245 wad_objects "$@" > "${wads}" 246 247 xargs -I {} sh -c \ 248 "if [ ! -f \"${GIT_WAD_OBJDIR}/\$1\" ]; then echo \"\$1\"; fi" -- {} \ 249 < "${wads}" 250 } 251 252 # Build the next_timestamp command, which returns the timestamp one 253 # second into the future. The date is formatted as expected by the 254 # restore function (see below), i.e. as the date "+%Y%m%D%H%M.%S" does 255 # for the current date. 256 restore_init() 257 { 258 259 # In shell, there is no standard method for converting a number of 260 # seconds from January 1, 1970 to midnight, into a specific format 261 # like the date command does for the current time. Some awk 262 # implementations provide the strftime function, but others do not. 263 # That said, the c99 C compiler has been standard since POSIX 2001, 264 # and the strftime function in charge of converting a date is a 265 # function of the standard C library. A C program can therefore be 266 # used to format a date on any POSIX-compatible system. This is the 267 # purpose of the program below. 268 # 269 # Note that it would be more efficient to pre-build the program and 270 # install it with git-wad. Its on-demand compilation nevertheless 271 # keeps the design more concise (everything is done in one place), 272 # while the compilation overhead should remain negligible overall. All 273 # the more so with several files to restore. Hence this choice of 274 # implementation, until performance problems really came to light. 275 cat << EOF > "${next_timestamp}.c" 276 #define _POSIX_C_SOURCE 200809L 277 #include <time.h> 278 #include <stdio.h> 279 280 int main(void) 281 { 282 char buf[32]; 283 time_t epoch; 284 struct tm* tm; 285 286 if((epoch = time(NULL)) == (time_t)-1) return 1; 287 ++epoch; 288 if((tm = localtime(&epoch)) == NULL) return 1; 289 if((strftime(buf, sizeof(buf), "%Y%m%d%H%M.%S", tm)) == 0) return 1; 290 printf("%s\n", buf); 291 return 0; 292 } 293 EOF 294 "${CC}" "${next_timestamp}.c" -o "${next_timestamp}" > /dev/null 2>&1 295 } 296 297 restore() # WAD file 298 { 299 wad="$1" 300 digest=$(sed__ "s/^${GIT_WAD_HEADER} \([0-9a-z]\{64\}\) [0-9]\{1,\}$/\1/" "${wad}") 301 302 if [ -z "${digest}" ]; then 303 >&2 printf "Invalid WAD file %s\n" "$1" 304 return 1 305 fi 306 307 if [ ! -f "${GIT_WAD_OBJDIR}"/"${digest}" ]; then 308 >&2 printf "WAD object unavailable %s %s\n" "${digest}" "${wad}" 309 else 310 >&2 printf "Restoring %s\n" "${wad}" 311 312 # Forces re-execution of the smudge filter to restore the WAD file. 313 # Note that git caches the state of the last time the file was 314 # smudged. And there's no specific way to invalidate this cache. The 315 # easiest way seems to be to update the file's modification time by 316 # touching it. But you have to make sure that this checkout doesn't 317 # occur in the same second as the previous one. This is why we 318 # explicitly set the file's timestamp to the next timestamp, i.e. 319 # the timestamp one second in the future. 320 timestamp="$("${next_timestamp}")" 321 touch -t "${timestamp}" "${wad}" 322 git checkout-index --index --force "${wad}" 323 fi 324 } 325 326 checksum() # [-c] 327 { 328 if command -v sha256sum 1> /dev/null 2>&1; then 329 sha256sum "$@" 330 331 elif command -v shasum 1> /dev/null 2>&1; then 332 shasum -a 256 "$@" 333 334 elif command -v sha256 1> /dev/null 2>&1; then 335 336 # Calculate checksum 337 if [ "$#" -eq 0 ] || [ "$1" != "-c" ]; then 338 sha256 339 340 # Check checksum 341 else 342 shift 1 # Discard the -c option 343 344 while read -r i; do 345 ref="$(echo "${i}" | cut -d' ' -f1)" 346 file="$(echo "${i}" | cut -d' ' -f3)" 347 348 sum="$(sha256 < "${file}")" 349 if [ "${ref}" = "${sum}" ]; then 350 printf '%s: OK\n' "${file}" 351 else 352 printf '%s: FAILED\n' "${file}" 353 fi 354 done 355 fi 356 fi 357 } 358 359 ######################################################################## 360 # Git filters (plumbing) 361 ######################################################################## 362 log() # str [, arg...] 363 { 364 if [ -n "${GIT_WAD_VERBOSE}" ] && [ ! "${GIT_WAD_VERBOSE}" -eq 0 ]; then 365 # shellcheck disable=SC2059 366 >&2 printf "$@" 367 fi 368 } 369 370 clean() # stdin 371 { 372 tmpclean="${git_wad_tmpdir}/tmpclean" 373 digest=$(cat - | tee "${tmpclean}" | checksum | cut -d' ' -f1) 374 size=$(wc -c < "${tmpclean}") 375 376 # Copy all bytes that could correspond to a WAD header. Note that null 377 # bytes are replaced by a 0 to avoid a warning about ignored null 378 # bytes: they cannot be stored in a variable. Replacing them is not a 379 # problem, since the header string you're looking for doesn't contain 380 # any. You just have to be careful not to replace them with a 381 # character belonging to the WAD header, which could lead to random 382 # bytes being mistakenly translated into a WAD header, as searched for 383 # in the following. 384 header_size="$(sizeof_header)" 385 header="$(dd ibs=1 count="${header_size}" if="${tmpclean}" 2>/dev/null\ 386 | tr__ '\0' '0')" 387 388 # Do not clean input stream if it is an un-smudged WAD 389 if [ "${header}" = "${GIT_WAD_HEADER}" ]; then 390 cat "${tmpclean}" 391 return 0 392 fi 393 394 # The input stream is already managed by git-wad 395 if [ -f "${GIT_WAD_OBJDIR}/${digest}" ]; then 396 log "git-wad:filter-clean: cache already exists %s\n"\ 397 "${GIT_WAD_OBJDIR}/${digest}" 398 399 # Store input stream to a file whose name is the stream digest 400 else 401 chmod 444 "${tmpclean}" 402 mv "${tmpclean}" "${GIT_WAD_OBJDIR}/${digest}" 403 log "git-wad:filter-clean: caching to %s\n" \ 404 "${GIT_WAD_OBJDIR}/${digest}" 405 fi 406 407 encode "${digest}" "${size}" 408 } 409 410 smudge() # stdin 411 { 412 header_size="$(sizeof_header)" 413 header="$(dd ibs=1 count="${header_size}" 2> /dev/null | tr__ '\0' '0')" 414 415 if [ "${header}" != "${GIT_WAD_HEADER}" ]; then # It is not a WAD 416 log "git-wad:filter-smudge: not a managed file" 417 printf "%s" "${header}" 418 cat - 419 420 else # It is a WAD 421 # The sed directive remove the space before the digest 422 digest_size="$(cat - | sed__ "1s/^ //")" 423 424 digest="$(echo "${digest_size}" | cut -d' ' -f1)" 425 size="$(echo "${digest_size}" | cut -d' ' -f2)" 426 object="${GIT_WAD_OBJDIR}/${digest}" 427 428 if [ -f "${object}" ]; then 429 log "git-wad:filter-smudge: restoring from %s\n" "${object}" 430 cat "${object}" 431 else 432 log "git-wad:filter-smudge: WAD object is missing %s\n" "${object}" 433 encode "${digest}" "${size}" 434 fi 435 fi 436 } 437 438 ######################################################################## 439 # Git sub commands (porcelain) 440 ######################################################################## 441 # Setup git filters 442 init() 443 { 444 # shellcheck disable=SC2310 445 if is_init; then 446 >&2 printf "git-wad is already initialized (check .git/config)\n" 447 else 448 git config filter.wad.clean "git-wad filter-clean" 449 git config filter.wad.smudge "git-wad filter-smudge" 450 >&2 printf "git-wad is initialized\n" 451 fi 452 } 453 454 # Transfert WAD objects to remote server 455 push() # [-1a] 456 { 457 if [ -z "${GIT_WAD_REMOTE_PUSH}" ]; then 458 >&2 printf "Remote undefined, i.e. variable GIT_WAD_REMOTE_PUSH is empty\n" 459 exit 1 460 fi 461 462 >&2 printf "Pushing to %s\n" "${GIT_WAD_REMOTE_PUSH}" 463 464 remote="${GIT_WAD_REMOTE_PUSH#file://}" 465 466 objects_to_push="${git_wad_tmpdir}/objects_to_push" 467 # shellcheck disable=SC2310 468 objects_to_push "$@" > "${objects_to_push}" 469 470 rsync -av --progress --ignore-existing \ 471 --files-from=- "${GIT_WAD_OBJDIR}" "${remote}" < "${objects_to_push}" 472 } 473 474 # Download WAD objects from remote server 475 fetch() # [-1a] 476 { 477 if [ -z "${GIT_WAD_REMOTE_FETCH}" ]; then 478 >&2 printf "Remote undefined, i.e. variable GIT_WAD_REMOTE_FETCH is empty\n" 479 exit 1 480 fi 481 482 objects_to_fetch="${git_wad_tmpdir}/objects_to_fetch" 483 objects_to_fetch "$@" > "${objects_to_fetch}" 484 485 >&2 printf "Fetching from %s\n" "${GIT_WAD_REMOTE_FETCH}" 486 487 # Use curl to download WAD objects via http[s] or gopher[s] protocol 488 if echo "${GIT_WAD_REMOTE_FETCH}" | grep -q \ 489 -e "^http[s]\{0,1\}://" \ 490 -e "^gopher[s]\{0,1\}://"; then 491 xargs -I {} curl -w "{}\n" \ 492 -o "${GIT_WAD_OBJDIR}/{}" "${GIT_WAD_REMOTE_FETCH}/{}" \ 493 < "${objects_to_fetch}" 494 495 # By default transfert WAD objects by rsync 496 else 497 remote="${GIT_WAD_REMOTE_FETCH#file://}" 498 rsync -av --progress --ignore-existing \ 499 --files-from=- "${remote}" "${GIT_WAD_OBJDIR}" \ 500 < "${objects_to_fetch}" 501 fi 502 } 503 504 # Check the integrity of the stored WAD content 505 fsck() 506 { 507 remove=0 508 509 OPTIND=1 510 while getopts ":r" opt; do 511 case "${opt}" in 512 r) remove=1 ;; 513 *) 514 >&2 printf "Invalid option -- %s\n" "${OPTARG}" 515 return 1 516 ;; 517 esac 518 done 519 520 wads="${git_wad_tmpdir}/wads" 521 all_objects > "${wads}" 522 n=$(wc -l "${wads}" | cut -d' ' -f1) 523 [ "${n}" -eq 0 ] && return # No WAD, i.e. nothing to do 524 525 # Prepare checksum verification of WADs, i.e. list one line per file 526 # starting with the WAD's checksum (actually its name) followed by 2 527 # spaces and its path. 528 sums="${git_wad_tmpdir}/sums" 529 xargs -I{} printf '%s %s/%s\n' "{}" "${GIT_WAD_OBJDIR}" "{}" \ 530 < "${wads}" > "${sums}" 531 532 # Check WAD checksum. 533 # 534 # Redirect the error stream to standard output to ensure that messages 535 # are printed in the order in which they are sent. By using a pipe, 536 # the shell starts a new process for the tee command which, if it only 537 # processes normal messages, will intertwine with the error messages 538 # from the checksum command. 539 result="${git_wad_tmpdir}/result" 540 checksum -c < "${sums}" 2>&1 | tee "${result}" 541 542 # Remove corrupted files 543 if [ ! "${remove}" -eq 0 ]; then 544 corrupted_wads="${git_wad_tmpdir}/corrupted_wads" 545 sed__ -n 's/^\(.\+\): FAILED$/\1/p' "${result}" > "${corrupted_wads}" 546 sed__ 's/^/remove /g' "${corrupted_wads}" 547 xargs -I{} rm -f "{}" < "${corrupted_wads}" 548 fi 549 } 550 551 # Restore the content of WAD files 552 checkout() 553 { 554 # shellcheck disable=SC2310 555 if ! is_init; then 556 >&2 printf "\e[0;31mgit-wad is not initialized\e[0m\n" 557 >&2 printf " (use \"git wad init\" to enable WAD management)\n" 558 exit 1 559 fi 560 561 restore_init 562 563 # Lists the files. Ensure verbatim filename (-z option) 564 # | Ensure one entry per line (see the output section of git-ls-tree(1)) 565 # | Restore content 566 git ls-files -z "${working_tree}" \ 567 | tr__ '\0' '\n' \ 568 | while read -r i; do 569 # Search for the git-wad header only in the first few bytes of the 570 # file to avoid unnecessarily processing all of the data; the 571 # git-wad header appears at the very beginning of the file, and once 572 # restored, searching the entire file can take a long time because 573 # it can be very large. Thus, considering only a few bytes is not 574 # only reliable, but also significantly speeds up the "checkout" 575 # command. 576 bytes=$(dd if="${i}" bs=128 count=1 2> /dev/null | tr__ '\0' '0') 577 if printf '%s' "${bytes}" \ 578 | grep -qe "^${GIT_WAD_HEADER} [0-9a-z]\{64\} [0-9]\{1,\}$" 579 then 580 restore "${i}" 581 fi 582 done 583 } 584 585 # Download WAD objects from remote server and restore the content of 586 # working WAD files. 587 pull() # [-1a] 588 { 589 fetch "$@" 590 checkout 591 } 592 593 # Delete WAD objects not used in the current work tree from local 594 # storage 595 prune() # [-1a] 596 { 597 unreferenced="${git_wad_tmpdir}/unreferenced_objects" 598 unreferenced_objects "$@" > "${unreferenced}" 599 600 xargs -I {} sh -c \ 601 ">&2 printf \"Removing %s\n\" \"${GIT_WAD_OBJDIR}/\$1\"; \ 602 rm -f \"${GIT_WAD_OBJDIR}/\$1\"" -- {} < "${unreferenced}" 603 } 604 605 # Print WAD management status 606 status() # [-1a] 607 { 608 # First, report the static of git wad initialization. 609 # shellcheck disable=SC2310 610 if ! is_init; then 611 printf "\e[0;31mgit-wad is not initialized\e[0m\n" 612 printf " (use \"git wad init\" to enable WAD management)\n" 613 printf "\n" 614 fi 615 616 # Check that a commit exists, otherwise there's no active HEAD and 617 # therefore no more status to report. In fact, the function would 618 # return an error if it queried the HEAD object. 619 if ! git rev-parse HEAD > /dev/null 2>&1; then 620 >&2 printf "No commits yet\n" 621 return 622 fi 623 624 # Create 3 temp files to list resolved, unrestored and orphaned WADs 625 resolved="${git_wad_tmpdir}/resolved" 626 unrestored="${git_wad_tmpdir}/unrestored" 627 orphaned="${git_wad_tmpdir}/orphaned" 628 wad_paths | while read -r wad; do 629 # Read the WAD bytes corresponding to its header before it is restored 630 header_size="$(sizeof_header)" 631 header="$(dd if="${wad}" ibs=1 count="${header_size}" 2> /dev/null \ 632 | tr__ '\0' '0')" 633 634 # The header read is not a valid WAD header, i.e. the WAD has 635 # already been restored 636 if [ "${header}" != "${GIT_WAD_HEADER}" ]; then 637 printf "%s\n" "${wad}" >> "${resolved}" 638 639 else 640 # Read the WAD digest from its header 641 digest=$(sed__ \ 642 -e "1s/^${GIT_WAD_HEADER} \([0-9a-z]\{64\}\) [0-9]\{1,\}$/\1/" \ 643 "${wad}") 644 645 # Check whether a file corresponding to the WAD digest exists 646 # locally. If so, the WAD file is simply not restored. Otherwise, 647 # it is orphaned of its data. 648 if [ -f "${GIT_WAD_OBJDIR}/${digest}" ]; then 649 printf "%s\n" "${wad}" >> "${unrestored}" 650 else 651 printf "%s\n" "${wad}" >> "${orphaned}" 652 fi 653 fi 654 done 655 656 # List resolved WADs, if any 657 if [ -s "${resolved}" ]; then 658 printf "Resolved WADs:\n" 659 sort "${resolved}" | sed 's/./\\&/g' \ 660 | xargs -I{} printf "\t\e[0;32m%s\e[0m\n" {} 661 printf "\n" 662 fi 663 664 # List un-restored WADs, if any 665 if [ -s "${unrestored}" ]; then 666 printf "Unrestored WADs:\n" 667 printf " (use \"git wad checkout\" to restore WADs)\n" 668 sort "${unrestored}" | sed 's/./\\&/g' \ 669 | xargs -I {} printf "\t\e[0;33m%s\e[0m\n" {} 670 printf "\n" 671 fi 672 673 # List orphaned WADs, if any 674 if [ -s "${orphaned}" ]; then 675 printf "Orphaned WADs:\n" 676 printf " (use \"git wad pull\" to download and restore WADs)\n" 677 sort "${orphaned}" | sed 's/./\\&/g' \ 678 | xargs -I {} printf "\t\e[0;31m%s\e[0m\n" {} 679 printf "\n" 680 fi 681 682 # Print number of WADs not referenced in current work tree 683 unreferenced="${git_wad_tmpdir}/unreferenced" 684 unreferenced_objects "$@" > "${unreferenced}" 685 n="$(wc -l < "${unreferenced}" | cut -d' ' -f1)" 686 if [ "${n}" -gt 0 ]; then 687 printf "There are %d WADs not use in the current working tree\n" "${n}" 688 printf " (use \"git wad prune\" to remove them)\n" 689 fi 690 } 691 692 ######################################################################## 693 # The command 694 ######################################################################## 695 mkdir -p "${GIT_WAD_OBJDIR}" 696 697 sub_cmd="$1" 698 case "${sub_cmd}" in 699 "checkout") shift 1; checkout ;; 700 "fetch") shift 1; fetch "$@" ;; 701 "filter-clean") shift 1; clean "$@" ;; 702 "filter-smudge") shift 1; smudge "$@" ;; 703 "fsck") shift 1; fsck "$@" ;; 704 "init") shift 1; init ;; 705 "prune") shift 1; prune "$@" ;; 706 "push") shift 1; push "$@" ;; 707 "pull") shift 1; pull "$@" ;; 708 "status") shift 1; status "$@" ;; 709 *) synopsis; exit 1 ;; 710 esac