1#!/bin/sh 2# 3# SPDX-License-Identifier: BSD-2-Clause 4# 5# Copyright (c) 2019-2021 Mark Johnston <markj@FreeBSD.org> 6# Copyright (c) 2021 John Baldwin <jhb@FreeBSD.org> 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions are 10# met: 11# 1. Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# 2. Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in 15# the documentation and/or other materials provided with the distribution. 16# 17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27# SUCH DAMAGE. 28# 29 30# TODO: 31# - roll back after errors or SIGINT 32# - created revs 33# - main (for git arc stage) 34 35warn() 36{ 37 echo "$(basename "$0"): $1" >&2 38} 39 40err() 41{ 42 warn "$1" 43 exit 1 44} 45 46cleanup() 47{ 48 rc=$? 49 rm -fr "$GITARC_TMPDIR" 50 trap - EXIT 51 exit $rc 52} 53 54err_usage() 55{ 56 cat >&2 <<__EOF__ 57Usage: git arc [-vy] <command> <arguments> 58 59Commands: 60 create [-dl] [-r <reviewer1>[,<reviewer2>...]] [-s subscriber[,...]] [<commit>|<commit range>] 61 list <commit>|<commit range> 62 patch [-bcrs] <diff1> [<diff2> ...] 63 stage [-b branch] [<commit>|<commit range>] 64 update [-l] [-m message] [<commit>|<commit range>] 65 66See git-arc(1) for details. 67__EOF__ 68 exit 1 69} 70 71# Use xmktemp instead of mktemp when creating temporary files. 72xmktemp() 73{ 74 mktemp "${GITARC_TMPDIR:?}/tmp.XXXXXXXXXX" || exit 1 75} 76 77# 78# Fetch the value of a boolean config variable ($1) and return true 79# (0) if the variable is true. The default value to use if the 80# variable is not set is passed in $2. 81# 82get_bool_config() 83{ 84 test "$(git config --bool --get $1 2>/dev/null || echo $2)" != "false" 85} 86 87# 88# Invoke the actual arc command. This allows us to only rely on the 89# devel/arcanist-lib port, which installs the actual script, rather than 90# the devel/arcanist-port, which installs a symlink in ${LOCALBASE}/bin 91# but conflicts with the archivers/arc port. 92# 93: ${LOCALBASE:=$(sysctl -n user.localbase)} 94: ${LOCALBASE:=/usr/local} 95: ${ARC_CMD:=${LOCALBASE}/lib/php/arcanist/bin/arc} 96arc() 97{ 98 ${ARC_CMD} "$@" 99} 100 101# 102# Filter the output of call-conduit to remove the warnings that are generated 103# for some installations where openssl module is mysteriously installed twice so 104# a warning is generated. It's likely a local config error, but we should work 105# in the face of that. 106# 107arc_call_conduit() 108{ 109 arc call-conduit "$@" | grep -v '^Warning: ' 110} 111 112# 113# Filter the output of arc list to remove the warnings as above, as well as 114# the bolding sequence (the color sequence remains intact). 115# 116arc_list() 117{ 118 arc list "$@" | grep -v '^Warning: ' | sed -E 's/\x1b\[1m//g;s/\x1b\[m//g' 119} 120 121diff2phid() 122{ 123 local diff 124 125 diff=$1 126 if ! expr "$diff" : 'D[1-9][0-9]*$' >/dev/null; then 127 err "invalid diff ID $diff" 128 fi 129 130 echo '{"names":["'"$diff"'"]}' | 131 arc_call_conduit -- phid.lookup | 132 jq -r "select(.response != []) | .response.${diff}.phid" 133} 134 135phid2diff() 136{ 137 local diff phid 138 139 phid=$1 140 if ! expr "$phid" : 'PHID-DREV-[0-9A-Za-z]*$' >/dev/null; then 141 err "invalid diff PHID $phid" 142 fi 143 diff=$(echo '{"constraints": {"phids": ["'"$phid"'"]}}' | 144 arc_call_conduit -- differential.revision.search | 145 jq -r '.response.data[0].id') 146 echo "D${diff}" 147} 148 149diff2status() 150{ 151 local diff tmp status summary 152 153 diff=$1 154 if ! expr "$diff" : 'D[1-9][0-9]*$' >/dev/null; then 155 err "invalid diff ID $diff" 156 fi 157 158 tmp=$(xmktemp) 159 echo '{"names":["'"$diff"'"]}' | 160 arc_call_conduit -- phid.lookup > "$tmp" 161 status=$(jq -r "select(.response != []) | .response.${diff}.status" < "$tmp") 162 summary=$(jq -r "select(.response != []) | 163 .response.${diff}.fullName" < "$tmp") 164 printf "%-14s %s\n" "${status}" "${summary}" 165} 166 167diff2parents() 168{ 169 local dep dependencies diff parents phid 170 171 diff=$1 172 phid=$(diff2phid "$diff") 173 for dep in $(echo '{"phids": ["'"$phid"'"]}' | 174 arc_call_conduit -- differential.query | 175 jq -r '.response[0].auxiliary."phabricator:depends-on"[]'); do 176 echo $(phid2diff $dep) 177 done 178} 179 180log2diff() 181{ 182 local diff 183 184 diff=$(git show -s --format=%B "$commit" | 185 sed -nE '/^Differential Revision:[[:space:]]+(https:\/\/reviews.freebsd.org\/)?(D[0-9]+)$/{s//\2/;p;}') 186 if [ -n "$diff" ] && [ "$(echo "$diff" | wc -l)" -eq 1 ]; then 187 echo "$diff" 188 else 189 echo 190 fi 191} 192 193# Look for an open revision with a title equal to the input string. Return 194# a possibly empty list of Differential revision IDs. 195title2diff() 196{ 197 local title 198 199 title=$(echo "$1" | sed 's/"/\\"/g') 200 arc_list --no-ansi | 201 awk -F': ' '{ 202 if (substr($0, index($0, FS) + length(FS)) == "'"$title"'") { 203 print substr($1, match($1, "D[1-9][0-9]*")) 204 } 205 }' 206} 207 208commit2diff() 209{ 210 local commit diff title 211 212 commit=$1 213 214 # First, look for a valid differential reference in the commit 215 # log. 216 diff=$(log2diff "$commit") 217 if [ -n "$diff" ]; then 218 echo "$diff" 219 return 220 fi 221 222 # Second, search the open reviews returned by 'arc list' looking 223 # for a subject match. 224 title=$(git show -s --format=%s "$commit") 225 diff=$(title2diff "$title") 226 if [ -z "$diff" ]; then 227 err "could not find review for '${title}'" 228 elif [ "$(echo "$diff" | wc -l)" -ne 1 ]; then 229 err "found multiple reviews with the same title" 230 fi 231 232 echo "$diff" 233} 234 235create_one_review() 236{ 237 local childphid commit doprompt draft msg parent parentphid reviewers 238 local subscribers 239 240 commit=$1 241 reviewers=$2 242 subscribers=$3 243 parent=$4 244 doprompt=$5 245 draft=$6 246 247 if [ "$doprompt" ] && ! show_and_prompt "$commit"; then 248 return 1 249 fi 250 251 if [ "$draft" -eq 1 ]; then 252 draft=--draft 253 else 254 unset draft 255 fi 256 257 msg=$(xmktemp) 258 git show -s --format='%B' "$commit" > "$msg" 259 printf "\nTest Plan:\n" >> "$msg" 260 printf "\nReviewers:\n" >> "$msg" 261 printf "%s\n" "${reviewers}" >> "$msg" 262 printf "\nSubscribers:\n" >> "$msg" 263 printf "%s\n" "${subscribers}" >> "$msg" 264 265 yes | EDITOR=true \ 266 arc diff --message-file "$msg" --never-apply-patches --create \ 267 --allow-untracked $draft $BROWSE --head "$commit" "${commit}~" 268 [ $? -eq 0 ] || err "could not create Phabricator diff" 269 270 if [ -n "$parent" ]; then 271 diff=$(commit2diff "$commit") 272 [ -n "$diff" ] || err "failed to look up review ID for $commit" 273 274 childphid=$(diff2phid "$diff") 275 parentphid=$(diff2phid "$parent") 276 echo '{ 277 "objectIdentifier": "'"${childphid}"'", 278 "transactions": [ 279 { 280 "type": "parents.add", 281 "value": ["'"${parentphid}"'"] 282 } 283 ]}' | 284 arc_call_conduit -- differential.revision.edit >&3 285 fi 286 return 0 287} 288 289# Get a list of reviewers who accepted the specified diff. 290diff2reviewers() 291{ 292 local diff reviewid userids 293 294 diff=$1 295 reviewid=$(diff2phid "$diff") 296 userids=$( \ 297 echo '{ 298 "constraints": {"phids": ["'"$reviewid"'"]}, 299 "attachments": {"reviewers": true} 300 }' | 301 arc_call_conduit -- differential.revision.search | 302 jq '.response.data[0].attachments.reviewers.reviewers[] | select(.status == "accepted").reviewerPHID') 303 if [ -n "$userids" ]; then 304 echo '{ 305 "constraints": {"phids": ['"$(echo $userids | tr '[:blank:]' ',')"']} 306 }' | 307 arc_call_conduit -- user.search | 308 jq -r '.response.data[].fields.username' 309 fi 310} 311 312prompt() 313{ 314 local resp 315 316 if [ "$ASSUME_YES" ]; then 317 return 0 318 fi 319 320 printf "\nDoes this look OK? [y/N] " 321 read -r resp 322 323 case $resp in 324 [Yy]) 325 return 0 326 ;; 327 *) 328 return 1 329 ;; 330 esac 331} 332 333show_and_prompt() 334{ 335 local commit 336 337 commit=$1 338 339 git show "$commit" 340 prompt 341} 342 343build_commit_list() 344{ 345 local chash _commits commits 346 347 for chash in "$@"; do 348 _commits=$(git rev-parse "${chash}") 349 if ! git cat-file -e "${chash}"'^{commit}' >/dev/null 2>&1; then 350 # shellcheck disable=SC2086 351 _commits=$(git rev-list --reverse $_commits) 352 fi 353 [ -n "$_commits" ] || err "invalid commit ID ${chash}" 354 commits="$commits $_commits" 355 done 356 echo "$commits" 357} 358 359gitarc__create() 360{ 361 local commit commits doprompt draft list o prev reviewers subscribers 362 363 list= 364 prev="" 365 if get_bool_config arc.list false; then 366 list=1 367 fi 368 doprompt=1 369 draft=0 370 while getopts dlp:r:s: o; do 371 case "$o" in 372 d) 373 draft=1 374 ;; 375 l) 376 list=1 377 ;; 378 p) 379 prev="$OPTARG" 380 ;; 381 r) 382 reviewers="$OPTARG" 383 ;; 384 s) 385 subscribers="$OPTARG" 386 ;; 387 *) 388 err_usage 389 ;; 390 esac 391 done 392 shift $((OPTIND-1)) 393 394 commits=$(build_commit_list "$@") 395 396 if [ "$list" ]; then 397 for commit in ${commits}; do 398 git --no-pager show --oneline --no-patch "$commit" 399 done | git_pager 400 if ! prompt; then 401 return 402 fi 403 doprompt= 404 fi 405 406 for commit in ${commits}; do 407 if create_one_review "$commit" "$reviewers" "$subscribers" "$prev" \ 408 "$doprompt" "$draft"; then 409 prev=$(commit2diff "$commit") 410 else 411 prev="" 412 fi 413 done 414} 415 416gitarc__list() 417{ 418 local chash commit commits diff openrevs title 419 420 commits=$(build_commit_list "$@") 421 openrevs=$(arc_list --ansi) 422 423 for commit in $commits; do 424 chash=$(git show -s --format='%C(auto)%h' "$commit") 425 printf "%s" "${chash} " 426 427 diff=$(log2diff "$commit") 428 if [ -n "$diff" ]; then 429 diff2status "$diff" 430 continue 431 fi 432 433 # This does not use commit2diff as it needs to handle errors 434 # differently and keep the entire status. 435 title=$(git show -s --format=%s "$commit") 436 diff=$(echo "$openrevs" | \ 437 awk -F'D[1-9][0-9]*: ' \ 438 '{if ($2 == "'"$(echo "$title" | sed 's/"/\\"/g')"'") print $0}') 439 if [ -z "$diff" ]; then 440 echo "No Review : $title" 441 elif [ "$(echo "$diff" | wc -l)" -ne 1 ]; then 442 printf "%s" "Ambiguous Reviews: " 443 echo "$diff" | grep -E -o 'D[1-9][0-9]*:' | tr -d ':' \ 444 | paste -sd ',' - | sed 's/,/, /g' 445 else 446 echo "$diff" | sed -e 's/^[^ ]* *//' 447 fi 448 done 449} 450 451# Try to guess our way to a good author name. The DWIM is strong in this 452# function, but these heuristics seem to generally produce the right results, in 453# the sample of src commits I checked out. 454find_author() 455{ 456 local addr name email author_addr author_name 457 458 addr="$1" 459 name="$2" 460 author_addr="$3" 461 author_name="$4" 462 463 # The Phabricator interface doesn't have a simple way to get author name and 464 # address, so we have to try a number of heuristics to get the right result. 465 466 # Choice 1: It's a FreeBSD committer. These folks have no '.' in their phab 467 # username/addr. Sampled data in phab suggests that there's a high rate of 468 # these people having their local config pointing at something other than 469 # freebsd.org (which isn't surprising for ports committers getting src 470 # commits reviewed). 471 case "${addr}" in 472 *.*) ;; # external user 473 guest-*) ;; # Fake email address, not a FreeBSD user 474 *) 475 echo "${name} <${addr}@FreeBSD.org>" 476 return 477 ;; 478 esac 479 480 # Choice 2: author_addr and author_name were set in the bundle, so use 481 # that. We may need to filter some known bogus ones, should they crop up. 482 if [ -n "$author_name" -a -n "$author_addr" ]; then 483 echo "${author_name} <${author_addr}>" 484 return 485 fi 486 487 # Choice 3: We can find this user in the FreeBSD repo. They've submited 488 # something before, and they happened to use an email that's somewhat 489 # similar to their phab username. 490 email=$(git log -1 --author "$(echo ${addr} | tr _ .)" --pretty="%aN <%aE>") 491 if [ -n "${email}" ]; then 492 echo "${email}" 493 return 494 fi 495 496 # Choice 4: We know this user. They've committed before, and they happened 497 # to use the same name, unless the name has the word 'user' in it. This 498 # might not be a good idea, since names can be somewhat common (there 499 # are two Andrew Turners that have contributed to FreeBSD, for example). 500 if ! (echo "${name}" | grep -w "[Uu]ser" -q); then 501 email=$(git log -1 --author "${name}" --pretty="%aN <%aE>") 502 if [ -n "$email" ]; then 503 echo "$email" 504 return 505 fi 506 fi 507 508 # Choice 5: Wing it as best we can. In this scenario, we replace the last _ 509 # with a @, and call it the email address... 510 # Annoying fun fact: Phab replaces all non alpha-numerics with _, so we 511 # don't know if the prior _ are _ or + or any number of other characters. 512 # Since there's issues here, prompt 513 a=$(printf "%s <%s>\n" "${name}" $(echo "$addr" | sed -e 's/\(.*\)_/\1@/')) 514 echo "Making best guess: Turning ${addr} to ${a}" >&2 515 if ! prompt; then 516 echo "ABORT" 517 return 518 fi 519 echo "${a}" 520} 521 522patch_branch() 523{ 524 local base new suffix 525 526 if [ $# -eq 1 ]; then 527 base="gitarc-$1" 528 else 529 base="gitarc-$(printf "%s-" "$@" | sed 's/-$//')" 530 fi 531 532 new="$base" 533 suffix=1 534 while git show-ref --quiet --branches "$new"; do 535 new="${base}_$suffix" 536 suffix=$((suffix + 1)) 537 done 538 539 git checkout -b "$new" 540} 541 542patch_commit() 543{ 544 local diff reviewid review_data authorid user_data user_addr user_name 545 local diff_data author_addr author_name author tmp 546 547 diff=$1 548 reviewid=$(diff2phid "$diff") 549 # Get the author phid for this patch 550 review_data=$(xmktemp) 551 echo '{"constraints": {"phids": ["'"$reviewid"'"]}}' | \ 552 arc_call_conduit -- differential.revision.search > "$review_data" 553 authorid=$(jq -r '.response.data[].fields.authorPHID' "$review_data") 554 # Get metadata about the user that submitted this patch 555 user_data=$(xmktemp) 556 echo '{"constraints": {"phids": ["'"$authorid"'"]}}' | \ 557 arc_call_conduit -- user.search | \ 558 jq -r '.response.data[].fields' > "$user_data" 559 user_addr=$(jq -r '.username' "$user_data") 560 user_name=$(jq -r '.realName' "$user_data") 561 # Dig the data out of querydiffs api endpoint, although it's deprecated, 562 # since it's one of the few places we can get email addresses. It's unclear 563 # if we can expect multiple difference ones of these. Some records don't 564 # have this data, so we remove all the 'null's. We sort the results and 565 # remove duplicates 'just to be sure' since we've not seen multiple 566 # records that match. 567 diff_data=$(xmktemp) 568 echo '{"revisionIDs": [ '"${diff#D}"' ]}' | \ 569 arc_call_conduit -- differential.querydiffs | 570 jq -r '.response | flatten | .[]' > "$diff_data" 571 # If the differential revision has multiple revisions, just take the first 572 # non-null value we get. 573 author_addr=$(jq -r ".authorEmail?" "$diff_data" | grep -v '^null$' | head -n 1) 574 author_name=$(jq -r ".authorName?" "$diff_data" | grep -v '^null$' | head -n 1) 575 576 author=$(find_author "$user_addr" "$user_name" "$author_addr" "$author_name") 577 578 # If we had to guess, and the user didn't want to guess, abort 579 if [ "${author}" = "ABORT" ]; then 580 warn "Not committing due to uncertainty over author name" 581 exit 1 582 fi 583 584 tmp=$(xmktemp) 585 jq -r '.response.data[].fields.title' "$review_data" > "$tmp" 586 echo >> "$tmp" 587 jq -r '.response.data[].fields.summary' "$review_data" >> "$tmp" 588 echo >> "$tmp" 589 # XXX this leaves an extra newline in some cases. 590 reviewers=$(diff2reviewers "$diff" | sed '/^$/d' | paste -sd ',' - | sed 's/,/, /g') 591 if [ -n "$reviewers" ]; then 592 printf "Reviewed by:\t%s\n" "${reviewers}" >> "$tmp" 593 fi 594 # XXX TODO refactor with gitarc__stage maybe? 595 printf "Differential Revision:\thttps://reviews.freebsd.org/%s\n" "${diff}" >> "$tmp" 596 git commit --author "${author}" --file "$tmp" 597} 598 599apply_rev() 600{ 601 local commit parent parents raw rev stack 602 603 rev=$1 604 commit=$2 605 raw=$3 606 stack=$4 607 608 if $stack; then 609 parents=$(diff2parents "$rev") 610 for parent in $parents; do 611 echo "Applying parent ${parent}..." 612 if ! apply_rev $parent $commit $raw $stack; then 613 return 1 614 fi 615 done 616 fi 617 618 if $raw; then 619 fetch -o /dev/stdout "https://reviews.freebsd.org/${rev}.diff" | git apply --index 620 else 621 arc patch --skip-dependencies --nobranch --nocommit --force $rev 622 fi 623 624 if ${commit}; then 625 patch_commit $rev 626 fi 627 return 0 628} 629 630gitarc__patch() 631{ 632 local branch commit o raw rev stack 633 634 branch=false 635 commit=false 636 raw=false 637 stack=false 638 while getopts bcrs o; do 639 case "$o" in 640 b) 641 require_clean_work_tree "patch -b" 642 branch=true 643 ;; 644 c) 645 require_clean_work_tree "patch -c" 646 commit=true 647 ;; 648 r) 649 raw=true 650 ;; 651 s) 652 stack=true 653 ;; 654 *) 655 err_usage 656 ;; 657 esac 658 done 659 shift $((OPTIND-1)) 660 661 if [ $# -eq 0 ]; then 662 err_usage 663 fi 664 665 if ${branch}; then 666 patch_branch "$@" 667 fi 668 for rev in "$@"; do 669 echo "Applying ${rev}..." 670 apply_rev $rev $commit $raw $stack 671 done 672} 673 674gitarc__stage() 675{ 676 local author branch commit commits diff reviewers title tmp 677 678 branch=main 679 while getopts b: o; do 680 case "$o" in 681 b) 682 branch="$OPTARG" 683 ;; 684 *) 685 err_usage 686 ;; 687 esac 688 done 689 shift $((OPTIND-1)) 690 691 commits=$(build_commit_list "$@") 692 693 if [ "$branch" = "main" ]; then 694 git checkout -q main 695 else 696 git checkout -q -b "${branch}" main 697 fi 698 699 tmp=$(xmktemp) 700 for commit in $commits; do 701 git show -s --format=%B "$commit" > "$tmp" 702 title=$(git show -s --format=%s "$commit") 703 diff=$(title2diff "$title") 704 if [ -n "$diff" ]; then 705 # XXX this leaves an extra newline in some cases. 706 reviewers=$(diff2reviewers "$diff" | sed '/^$/d' | paste -sd ',' - | sed 's/,/, /g') 707 if [ -n "$reviewers" ]; then 708 printf "Reviewed by:\t%s\n" "${reviewers}" >> "$tmp" 709 fi 710 printf "Differential Revision:\thttps://reviews.freebsd.org/%s" "${diff}" >> "$tmp" 711 fi 712 author=$(git show -s --format='%an <%ae>' "${commit}") 713 if ! git cherry-pick --no-commit "${commit}"; then 714 warn "Failed to apply $(git rev-parse --short "${commit}"). Are you staging patches in the wrong order?" 715 git checkout -f 716 break 717 fi 718 git commit --edit --file "$tmp" --author "${author}" 719 done 720} 721 722gitarc__update() 723{ 724 local commit commits diff doprompt have_msg list o msg 725 726 list= 727 if get_bool_config arc.list false; then 728 list=1 729 fi 730 doprompt=1 731 while getopts lm: o; do 732 case "$o" in 733 l) 734 list=1 735 ;; 736 m) 737 msg="$OPTARG" 738 have_msg=1 739 ;; 740 *) 741 err_usage 742 ;; 743 esac 744 done 745 shift $((OPTIND-1)) 746 747 commits=$(build_commit_list "$@") 748 749 if [ "$list" ]; then 750 for commit in ${commits}; do 751 git --no-pager show --oneline --no-patch "$commit" 752 done | git_pager 753 if ! prompt; then 754 return 755 fi 756 doprompt= 757 fi 758 759 for commit in ${commits}; do 760 diff=$(commit2diff "$commit") 761 762 if [ "$doprompt" ] && ! show_and_prompt "$commit"; then 763 break 764 fi 765 766 # The linter is stupid and applies patches to the working copy. 767 # This would be tolerable if it didn't try to correct "misspelled" variable 768 # names. 769 if [ -n "$have_msg" ]; then 770 arc diff --message "$msg" --allow-untracked --never-apply-patches \ 771 --update "$diff" --head "$commit" "${commit}~" 772 else 773 arc diff --allow-untracked --never-apply-patches --update "$diff" \ 774 --head "$commit" "${commit}~" 775 fi 776 done 777} 778 779set -e 780 781ASSUME_YES= 782if get_bool_config arc.assume-yes false; then 783 ASSUME_YES=1 784fi 785 786VERBOSE= 787while getopts vy o; do 788 case "$o" in 789 v) 790 VERBOSE=1 791 ;; 792 y) 793 ASSUME_YES=1 794 ;; 795 *) 796 err_usage 797 ;; 798 esac 799done 800shift $((OPTIND-1)) 801 802[ $# -ge 1 ] || err_usage 803 804[ -x "${ARC_CMD}" ] || err "arc is required, install devel/arcanist-lib" 805which jq >/dev/null 2>&1 || err "jq is required, install textproc/jq" 806 807if [ "$VERBOSE" ]; then 808 exec 3>&1 809else 810 exec 3> /dev/null 811fi 812 813case "$1" in 814create|list|patch|stage|update) 815 ;; 816*) 817 err_usage 818 ;; 819esac 820verb=$1 821shift 822 823# All subcommands require at least one parameter. 824if [ $# -eq 0 ]; then 825 err_usage 826fi 827 828# Pull in some git helper functions. 829git_sh_setup=$(git --exec-path)/git-sh-setup 830[ -f "$git_sh_setup" ] || err "cannot find git-sh-setup" 831SUBDIRECTORY_OK=y 832USAGE= 833# shellcheck disable=SC1090 834. "$git_sh_setup" 835 836# git commands use GIT_EDITOR instead of EDITOR, so try to provide consistent 837# behaviour. Ditto for PAGER. This makes git-arc play nicer with editor 838# plugins like vim-fugitive. 839if [ -n "$GIT_EDITOR" ]; then 840 EDITOR=$GIT_EDITOR 841fi 842if [ -n "$GIT_PAGER" ]; then 843 PAGER=$GIT_PAGER 844fi 845 846# Bail if the working tree is unclean, except for "list" and "patch" 847# operations. 848case $verb in 849list|patch) 850 ;; 851*) 852 require_clean_work_tree "$verb" 853 ;; 854esac 855 856if get_bool_config arc.browse false; then 857 BROWSE=--browse 858fi 859 860GITARC_TMPDIR=$(mktemp -d) || exit 1 861trap cleanup EXIT HUP INT QUIT TRAP USR1 TERM 862 863gitarc__"${verb}" "$@" 864