git-remote-gcrypt 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. #!/bin/sh
  2. #
  3. # git-remote-gcrypt
  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) version 2 or 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. # See README.rst for usage instructions
  19. set -e # errexit
  20. set -u # nounset
  21. set -f # noglob
  22. set -C # noclobber
  23. Localdir="${GIT_DIR:=.git}/remote-gcrypt"
  24. export GITCEPTION="${GITCEPTION:-}+" # Reuse $Gref except when stacked
  25. Gref="refs/gcrypt/gitception$GITCEPTION"
  26. Gref_rbranch="refs/heads/master"
  27. Packkey_bytes=63 # nbr random bytes for packfile keys, any >= 256 bit is ok
  28. Hashtype=SHA256 # SHA512 SHA384 SHA256 SHA224 supported.
  29. Manifestfile=91bd0c092128cf2e60e1a608c31e92caf1f9c1595f83f2890ef17c0e4881aa0a
  30. Hex40="[a-f0-9]"
  31. Hex40=$Hex40$Hex40$Hex40$Hex40$Hex40$Hex40$Hex40$Hex40
  32. Hex40=$Hex40$Hex40$Hex40$Hex40$Hex40 # Match SHA-1 hexdigest
  33. Did_find_repo= # yes for connected, no for no repo
  34. Repoid=
  35. Refslist=
  36. Packlist=
  37. Keeplist=
  38. Extnlist=
  39. Repack_limit=25
  40. Recipients=
  41. # compat/utility functions
  42. # xfeed: The most basic output function puts $1 into the stdin of $2..$#
  43. xfeed()
  44. {
  45. local input_=
  46. input_=$1; shift
  47. "$@" <<EOF
  48. $input_
  49. EOF
  50. }
  51. xecho() { xfeed "$*" cat; }
  52. xecho_n() { xecho "$@" | tr -d \\n ; } # kill newlines
  53. echo_git() { xecho "$@" ; } # Code clarity
  54. echo_info() { xecho "gcrypt:" "$@" >&2; }
  55. echo_die() { echo_info "$@" ; exit 1; }
  56. echo_kill() { echo_info "$@" ; kill $$; exit 1; }
  57. isnull() { case "$1" in "") return 0;; *) return 1;; esac; }
  58. isnonnull() { ! isnull "$1"; }
  59. iseq() { case "$1" in "$2") return 0;; *) return 1;; esac; }
  60. isnoteq() { ! iseq "$1" "$2"; }
  61. negate() { ! "$@"; }
  62. isurl() { isnull "${2%%$1://*}"; }
  63. islocalrepo() { isnull "${1##/*}" && [ ! -e "$1/HEAD" ]; }
  64. xgrep() { command grep "$@" || : ; }
  65. # setvar is used for named return variables
  66. # $1 *must* be a valid variable name, $2 is any value
  67. #
  68. # Conventions
  69. # return variable names are passed with a @ prefix
  70. # return variable functions use f_ prefix local vars
  71. # return var consumers use r_ prefix vars (or Titlecase globals)
  72. setvar()
  73. {
  74. isnull "${1##@*}" || echo_die "Missing @ for return variable: $1"
  75. eval ${1#@}=\$2
  76. }
  77. Newline="
  78. "
  79. # $1 is return var, $2 is value appended with newline separator
  80. append_to()
  81. {
  82. local f_append_tmp_=
  83. eval f_append_tmp_=\$${1#@}
  84. isnull "$f_append_tmp_" || f_append_tmp_=$f_append_tmp_$Newline
  85. setvar "$1" "$f_append_tmp_$2"
  86. }
  87. # Pick words from each line
  88. # $1 return variable name
  89. # $2 input value
  90. pick_fields_1_2()
  91. {
  92. local f_ret= f_one= f_two=
  93. while read f_one f_two _ # from << here-document
  94. do
  95. f_ret="$f_ret$f_one $f_two$Newline"
  96. done <<EOF
  97. $2
  98. EOF
  99. setvar "$1" "${f_ret#$Newline}"
  100. }
  101. # Take all lines matching $2 (full line)
  102. # $1 return variable name
  103. # $2 filter word
  104. # $3 input value
  105. # if $1 is a literal `!', the match is reversed (and arguments shift)
  106. # we instead remove all lines matching
  107. filter_to()
  108. {
  109. local f_neg= f_line= f_ret= IFS=
  110. isnoteq "$1" "!" || { f_neg=negate; shift; }
  111. IFS=$Newline
  112. for f_line in $3
  113. do
  114. $f_neg isnonnull "${f_line##$2}" || f_ret=$f_ret$f_line$Newline
  115. done
  116. setvar "$1" "${f_ret%$Newline}"
  117. }
  118. # Output the number of lines in $1
  119. line_count()
  120. {
  121. local IFS=
  122. IFS=$Newline
  123. set -- $1
  124. xecho "$#"
  125. }
  126. ## gitception part
  127. # Fetch giturl $1, file $2
  128. gitception_get()
  129. {
  130. # Take care to preserve FETCH_HEAD
  131. local ret_=: obj_id= fet_head="$GIT_DIR/FETCH_HEAD"
  132. [ -e "$fet_head" ] && command mv -f "$fet_head" "$fet_head.$$~" || :
  133. git fetch -q -f "$1" "$Gref_rbranch:$Gref" >/dev/null &&
  134. obj_id="$(git ls-tree "$Gref" | xgrep -E '\b'"$2"'$' | awk '{print $3}')" &&
  135. isnonnull "$obj_id" && git cat-file blob "$obj_id" && ret_=: ||
  136. { ret_=false && : ; }
  137. [ -e "$fet_head.$$~" ] && command mv -f "$fet_head.$$~" "$fet_head" || :
  138. $ret_
  139. }
  140. anon_commit()
  141. {
  142. GIT_AUTHOR_NAME="root" GIT_AUTHOR_EMAIL="root@localhost" \
  143. GIT_AUTHOR_DATE="1356994801 -0400" GIT_COMMITTER_NAME="root" \
  144. GIT_COMMITTER_EMAIL="root@localhost" \
  145. GIT_COMMITTER_DATE="1356994801 -0400" \
  146. git commit-tree "$@" <<EOF
  147. Initial commit
  148. EOF
  149. }
  150. # Get 'tree' from $1, change file $2 to obj id $3
  151. update_tree()
  152. {
  153. local tab_=" "
  154. # $2 is a filename from the repo format
  155. (git ls-tree "$1" | xgrep -v -E '\b'"$2"'$';
  156. xecho "100644 blob $3$tab_$2") | git mktree
  157. }
  158. # Put giturl $1, file $2
  159. # depends on previous GET to set $Gref and depends on PUT_FINAL later
  160. gitception_put()
  161. {
  162. local obj_id= tree_id= commit_id=
  163. obj_id=$(git hash-object -w --stdin) &&
  164. tree_id=$(update_tree "$Gref" "$2" "$obj_id") &&
  165. commit_id=$(anon_commit "$tree_id") &&
  166. git update-ref "$Gref" "$commit_id"
  167. }
  168. # Remove giturl $1, file $2
  169. # depends on previous GET like put
  170. gitception_remove()
  171. {
  172. local tree_id= commit_id= tab_=" "
  173. # $2 is a filename from the repo format
  174. tree_id=$(git ls-tree "$Gref" | xgrep -v -E '\b'"$2"'$' | git mktree) &&
  175. commit_id=$(anon_commit "$tree_id") &&
  176. git update-ref "$Gref" "$commit_id"
  177. }
  178. gitception_new_repo()
  179. {
  180. local commit_id= empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  181. # get any file to update Gref, and if it's not updated we create empty
  182. git update-ref -d "$Gref" || :
  183. gitception_get "$1" "x" 2>/dev/null >&2 || :
  184. git rev-parse -q --verify "$Gref" >/dev/null && return 0 ||
  185. commit_id=$(anon_commit "$empty_tree") &&
  186. git update-ref "$Gref" "$commit_id"
  187. }
  188. ## end gitception
  189. # Fetch repo $1, file $2, tmpfile in $3
  190. GET()
  191. {
  192. if isurl sftp "$1"
  193. then
  194. (exec 0>&-; curl -s -S -k "$1/$2") > "$3"
  195. elif isurl rsync "$1"
  196. then
  197. (exec 0>&-; rsync -I -W "${1#rsync://}"/"$2" "$3" >&2)
  198. elif islocalrepo "$1"
  199. then
  200. cat "$1/$2" > "$3"
  201. else
  202. gitception_get "${1#gitception://}" "$2" > "$3"
  203. fi
  204. }
  205. # Put repo $1, file $2 or fail, tmpfile in $3
  206. PUT()
  207. {
  208. if isurl sftp "$1"
  209. then
  210. curl -s -S -k --ftp-create-dirs -T "$3" "$1/$2"
  211. elif isurl rsync "$1"
  212. then
  213. rsync -I -W "$3" "${1#rsync://}"/"$2" >&2
  214. elif islocalrepo "$1"
  215. then
  216. cat >| "$1/$2" < "$3"
  217. else
  218. gitception_put "${1#gitception://}" "$2" < "$3"
  219. fi
  220. }
  221. # Put all PUT changes for repo $1 at once
  222. PUT_FINAL()
  223. {
  224. if isurl sftp "$1" || islocalrepo "$1" || isurl rsync "$1"
  225. then
  226. :
  227. else
  228. git push --quiet -f "${1#gitception://}" "$Gref:$Gref_rbranch"
  229. fi
  230. }
  231. # Put directory for repo $1
  232. PUTREPO()
  233. {
  234. if isurl sftp "$1"
  235. then
  236. :
  237. elif isurl rsync "$1"
  238. then
  239. rsync -q -r --exclude='*' "$Localdir/" "${1#rsync://}" >&2
  240. elif islocalrepo "$1"
  241. then
  242. mkdir -p "$1"
  243. else
  244. gitception_new_repo "${1#gitception://}"
  245. fi
  246. }
  247. # For repo $1, delete all newline-separated files in $2
  248. REMOVE()
  249. {
  250. local fn_=
  251. if isurl sftp "$1"
  252. then
  253. # FIXME
  254. echo_info "sftp: Ignore remove request $1/$2"
  255. elif isurl rsync "$1"
  256. then
  257. xfeed "$2" rsync -I -W -v -r --delete --include-from=- \
  258. --exclude='*' "$Localdir"/ "${1#rsync://}/" >&2
  259. elif islocalrepo "$1"
  260. then
  261. for fn_ in $2; do
  262. rm -f "$1"/"$fn_"
  263. done
  264. else
  265. for fn_ in $2; do
  266. gitception_remove "${1#gitception://}" "$fn_"
  267. done
  268. fi
  269. }
  270. CLEAN_FINAL()
  271. {
  272. if isurl sftp "$1" || islocalrepo "$1" || isurl rsync "$1"
  273. then
  274. :
  275. else
  276. git update-ref -d "$Gref" || :
  277. fi
  278. }
  279. ENCRYPT()
  280. {
  281. gpg --batch --force-mdc --compress-algo none --passphrase-fd 3 -c 3<<EOF
  282. $1
  283. EOF
  284. }
  285. DECRYPT()
  286. {
  287. gpg -q --batch --no-default-keyring --secret-keyring /dev/null \
  288. --keyring /dev/null --passphrase-fd 3 -d 3<<EOF
  289. $1
  290. EOF
  291. }
  292. # Encrypt to recipients $1
  293. PRIVENCRYPT()
  294. {
  295. set -- $1
  296. if isnonnull "$Conf_signkey"; then
  297. set -- "$@" -u "$Conf_signkey"
  298. fi
  299. gpg --compress-algo none -se "$@"
  300. }
  301. # $1 is the match for good signature, $2 is the textual signers list
  302. PRIVDECRYPT()
  303. {
  304. local status_=
  305. exec 4>&1 &&
  306. status_=$(gpg --status-fd 3 -q -d 3>&1 1>&4) &&
  307. xfeed "$status_" grep "^\[GNUPG:\] ENC_TO " >/dev/null &&
  308. (xfeed "$status_" grep -e "$1" >/dev/null || {
  309. echo_info "Failed to verify manifest signature!" &&
  310. echo_info "Only accepting signatories: ${2:-(none)}" &&
  311. return 1
  312. })
  313. }
  314. # Generate $1 random bytes
  315. genkey()
  316. {
  317. gpg --armor --gen-rand 1 "$1"
  318. }
  319. gpg_hash()
  320. {
  321. local hash_=
  322. hash_=$(gpg --with-colons --print-md "$1" | tr A-F a-f)
  323. hash_=${hash_#:*:}
  324. xecho "${hash_%:}"
  325. }
  326. # $1 type
  327. tempname()
  328. {
  329. xecho "$Localdir/tmp_$1_.$$"
  330. }
  331. # Pass the branch/ref by pipe to git
  332. safe_git_rev_parse()
  333. {
  334. git cat-file --batch-check 2>/dev/null |
  335. xgrep -v "missing" | cut -f 1 -d ' '
  336. }
  337. make_new_repo()
  338. {
  339. echo_info "Setting up new repository"
  340. PUTREPO "$URL"
  341. # Needed assumption: the same user should have no duplicate Repoid
  342. Repoid=":id:$(genkey 15)"
  343. iseq "${NAME#gcrypt::}" "$URL" ||
  344. git config "remote.$NAME.gcrypt-id" "$Repoid"
  345. echo_info "Remote ID is $Repoid"
  346. Extnlist="extn comment"
  347. }
  348. # $1 return var for goodsig match, $2 return var for signers text
  349. read_config()
  350. {
  351. local recp_= r_keyinfo= cap_= conf_keyring= conf_part= good_sig= signers_=
  352. Conf_signkey=$(git config --path user.signingkey || :)
  353. conf_keyring=$(git config --path gcrypt.keyring || :)
  354. conf_part=$(git config --get "remote.$NAME.gcrypt-participants" '.+' ||
  355. git config --get gcrypt.participants '.+' || :)
  356. # Figure out which keys we should encrypt to or accept signatures from
  357. if isnonnull "$conf_keyring" && isnull "$conf_part"
  358. then
  359. echo_info "WARNING: Setting gcrypt.keyring is deprecated," \
  360. "use gcrypt.participants instead."
  361. conf_part=$(gpg --no-default-keyring --keyring "$conf_keyring" \
  362. --with-colons --fast-list -k | grep ^pub | cut -f 5 -d :)
  363. fi
  364. if isnull "$conf_part" || iseq "$conf_part" simple
  365. then
  366. signers_="(default keyring)"
  367. Recipients="--throw-keyids --default-recipient-self"
  368. good_sig="^\[GNUPG:\] GOODSIG "
  369. setvar "$1" "$good_sig"
  370. setvar "$2" "$signers_"
  371. return 0
  372. fi
  373. for recp_ in $conf_part
  374. do
  375. filter_to @r_keyinfo "pub*" \
  376. "$(gpg --with-colons --fast-list -k "$recp_")"
  377. isnull "$r_keyinfo" || isnonnull "${r_keyinfo##*"$Newline"*}" ||
  378. echo_info "WARNING: '$recp_' matches multiple keys, using one"
  379. r_keyinfo=${r_keyinfo%%"$Newline"*}
  380. keyid_=$(xfeed "$r_keyinfo" cut -f 5 -d :)
  381. isnonnull "$keyid_" &&
  382. signers_="$signers_ $keyid_" &&
  383. append_to @good_sig "^\[GNUPG:\] GOODSIG $keyid_" || {
  384. echo_info "WARNING: Skipping missing key $recp_"
  385. continue
  386. }
  387. # Check 'E'ncrypt capability
  388. cap_=$(xfeed "$r_keyinfo" cut -f 12 -d :)
  389. iseq "${cap_#*E}" "$cap_" || Recipients="$Recipients -R $keyid_"
  390. done
  391. if isnull "$Recipients"
  392. then
  393. echo_info "You have not configured any keys you can encrypt to" \
  394. "for this repository"
  395. echo_info "Use ::"
  396. echo_info " git config gcrypt.participants YOURKEYID"
  397. exit 1
  398. fi
  399. setvar "$1" "$good_sig"
  400. setvar "$2" "$signers_"
  401. }
  402. ensure_connected()
  403. {
  404. local manifest_= r_repoid= r_name= url_frag= r_sigmatch= r_signers= \
  405. tmp_manifest=
  406. if isnonnull "$Did_find_repo"
  407. then
  408. return
  409. fi
  410. Did_find_repo=no
  411. read_config @r_sigmatch @r_signers
  412. iseq "${NAME#gcrypt::}" "$URL" || r_name=$NAME
  413. # Fixup ssh:// -> rsync://
  414. if isurl ssh "$URL"; then
  415. URL="rsync://${URL#ssh://}"
  416. isnull "$r_name" || {
  417. git config "remote.$r_name.url" "gcrypt::$URL"
  418. echo_info "Updated URL for $r_name, ssh: -> rsync:"
  419. }
  420. fi
  421. if isurl gitception "$URL" && isnonnull "$r_name"; then
  422. git config "remote.$r_name.url" "gcrypt::${URL#gitception://}"
  423. echo_info "Updated URL for $r_name, gitception:// -> ()"
  424. fi
  425. # Find the URL fragment
  426. url_frag=${URL##*"#"}
  427. isnoteq "$url_frag" "$URL" || url_frag=
  428. isnonnull "$url_frag" || {
  429. # find old style /G.XXXXXX fragment
  430. url_frag=${URL##*/G.}
  431. if isnoteq "$url_frag" "$URL"; then
  432. URL=${URL%/G."$url_frag"}
  433. isnull "$r_name" || {
  434. git config "remote.$r_name.url" \
  435. "gcrypt::$URL#$url_frag"
  436. echo_info "Updated URL for $r_name, use #fragment"
  437. }
  438. else
  439. url_frag=
  440. fi
  441. }
  442. URL=${URL%"#$url_frag"}
  443. # manifestfile -- sha224 hash if we can, else the default location
  444. if isurl sftp "$URL" || islocalrepo "$URL" || isurl rsync "$URL"
  445. then
  446. # not for gitception
  447. isnull "$url_frag" ||
  448. Manifestfile=$(xecho_n "$url_frag" | gpg_hash SHA224)
  449. else
  450. isnull "$url_frag" || Gref_rbranch="refs/heads/$url_frag"
  451. fi
  452. Repoid=
  453. isnull "$r_name" ||
  454. Repoid=$(git config "remote.$r_name.gcrypt-id" || :)
  455. tmp_manifest=$(tempname maniF)
  456. GET "$URL" "$Manifestfile" "$tmp_manifest" 2>/dev/null || {
  457. echo_info "Repository not found: $URL"
  458. return 0
  459. }
  460. Did_find_repo=yes
  461. echo_info "Decrypting manifest"
  462. manifest_=$(PRIVDECRYPT "$r_sigmatch" "$r_signers" < "$tmp_manifest") &&
  463. isnonnull "$manifest_" ||
  464. echo_die "Failed to decrypt manifest!"
  465. rm -f "$tmp_manifest"
  466. filter_to @Refslist "$Hex40 *" "$manifest_"
  467. filter_to @Packlist "pack :*:* *" "$manifest_"
  468. filter_to @Keeplist "keep :*:*" "$manifest_"
  469. filter_to @Extnlist "extn *" "$manifest_"
  470. filter_to @r_repoid "repo *" "$manifest_"
  471. r_repoid=${r_repoid#repo }
  472. r_repoid=${r_repoid% *}
  473. if isnull "$Repoid"
  474. then
  475. echo_info "Remote ID is $r_repoid"
  476. Repoid=$r_repoid
  477. elif isnoteq "$r_repoid" "$Repoid"
  478. then
  479. echo_info "WARNING:"
  480. echo_info "WARNING: Remote ID has changed!"
  481. echo_info "WARNING: from $Repoid"
  482. echo_info "WARNING: to $r_repoid"
  483. echo_info "WARNING:"
  484. Repoid=$r_repoid
  485. else
  486. return 0
  487. fi
  488. isnull "$r_name" || git config "remote.$r_name.gcrypt-id" "$r_repoid"
  489. }
  490. # $1 is the hash type (SHA256 etc)
  491. # $2 the pack id
  492. # $3 the key
  493. get_verify_decrypt_pack()
  494. {
  495. local rcv_id= tmp_encrypted=
  496. tmp_encrypted=$(tempname packF)
  497. GET "$URL" "$2" "$tmp_encrypted" &&
  498. rcv_id=$(gpg_hash "$1" < "$tmp_encrypted") &&
  499. iseq "$rcv_id" "$2" || echo_die "Packfile $2 does not match digest!"
  500. DECRYPT "$3" < "$tmp_encrypted"
  501. rm -f "$tmp_encrypted"
  502. }
  503. # download all packlines (pack :SHA256:a32abc1231) from stdin (or die)
  504. # $1 destdir (when repack, else "")
  505. get_pack_files()
  506. {
  507. local pack_id= r_pack_key_line= htype_= pack_= key_=
  508. while IFS=': ' read -r _ htype_ pack_ # <<here-document
  509. do
  510. isnonnull "$pack_" || continue
  511. # Get the Packlist line with the key
  512. pack_id=":${htype_}:$pack_"
  513. filter_to @r_pack_key_line "pack $pack_id *" "$Packlist"
  514. key_=${r_pack_key_line#pack $pack_id }
  515. if isnonnull "${pack_##$Hex40*}" ||
  516. isnoteq "$htype_" SHA256 && isnoteq "$htype_" SHA224 &&
  517. isnoteq "$htype_" SHA384 && isnoteq "$htype_" SHA512
  518. then
  519. echo_die "Packline malformed: $pack_id"
  520. fi
  521. get_verify_decrypt_pack "$htype_" "$pack_" "$key_" | \
  522. if isnull "${1:-}"
  523. then
  524. # add to local pack list
  525. git index-pack -v --stdin >/dev/null
  526. xecho "pack $pack_id" >> "$Localdir/have_packs$GITCEPTION"
  527. else
  528. git index-pack -v --stdin "$1/${pack_}.pack" >/dev/null
  529. fi
  530. done
  531. }
  532. # Download and unpack remote packfiles
  533. # $1 return var for list of packfiles to delete
  534. repack_if_needed()
  535. {
  536. local n_= m_= kline_= r_line= r_keep_packlist= r_del_list=
  537. isnonnull "$Packlist" || return 0
  538. if isnonnull "${GCRYPT_FULL_REPACK:-}"
  539. then
  540. Keeplist=
  541. Repack_limit=0
  542. fi
  543. pick_fields_1_2 @r_del_list "$Packlist"
  544. n_=$(line_count "$Packlist")
  545. m_=$(line_count "$Keeplist")
  546. if iseq 0 "$(( $Repack_limit < ($n_ - $m_) ))"; then
  547. return
  548. fi
  549. echo_info "Repacking remote $NAME, ..."
  550. rm -r -f "$Localdir/pack"
  551. mkdir -p "$Localdir/pack"
  552. # Split packages to keep and to repack
  553. if isnonnull "$Keeplist"; then
  554. while read -r _ kline_ _ # <<here-document
  555. do
  556. isnonnull "$kline_" || continue
  557. filter_to @r_line "pack $kline_ *" "$Packlist"
  558. append_to @r_keep_packlist "$r_line"
  559. filter_to ! @r_del_list "pack $kline_" "$r_del_list"
  560. done <<EOF
  561. $Keeplist
  562. EOF
  563. fi
  564. xfeed "$r_del_list" get_pack_files "$Localdir/pack/"
  565. (set +f; git verify-pack -v "$Localdir"/pack/*.idx ||
  566. echo_kill "git verify-pack failed!") |
  567. grep -E '^[0-9a-f]{40}' | cut -f 1 -d ' '
  568. Packlist=$r_keep_packlist
  569. setvar "$1" "$r_del_list"
  570. }
  571. do_capabilities()
  572. {
  573. echo_git fetch
  574. echo_git push
  575. echo_git
  576. }
  577. do_list()
  578. {
  579. local obj_id= ref_name= line_=
  580. ensure_connected
  581. xecho "$Refslist" | while read line_
  582. do
  583. isnonnull "$line_" || break
  584. obj_id=${line_%% *}
  585. ref_name=${line_##* }
  586. echo_git "$obj_id" "$ref_name"
  587. if iseq "$ref_name" "refs/heads/master"
  588. then
  589. echo_git "@refs/heads/master HEAD"
  590. fi
  591. done
  592. # end with blank line
  593. echo_git
  594. }
  595. do_fetch()
  596. {
  597. # Download packs in the manifest that don't appear in have_packs
  598. local pneed_= premote_=
  599. ensure_connected
  600. # The `+` for $GITCEPTION is pointless but we will be safe for stacking
  601. pick_fields_1_2 @premote_ "$Packlist"
  602. if [ -s "$Localdir/have_packs+" ]
  603. then
  604. pneed_=$(xfeed "$premote_" xgrep -v -x -f "$Localdir/have_packs+")
  605. else
  606. pneed_=$premote_
  607. fi
  608. xfeed "$pneed_" get_pack_files
  609. echo_git # end with blank line
  610. }
  611. # do_push PUSHARGS (multiple lines like +src:dst, with both + and src opt.)
  612. do_push()
  613. {
  614. # Security protocol:
  615. # Each git packfile is encrypted and then named for the encrypted
  616. # file's hash. The manifest is updated with the pack id.
  617. # The manifest is encrypted.
  618. local r_revlist= pack_id= key_= obj_= src_= dst_= \
  619. r_pack_delete= tmp_encrypted= tmp_objlist= tmp_manifest=
  620. ensure_connected
  621. if iseq "$Did_find_repo" "no"
  622. then
  623. make_new_repo
  624. fi
  625. if isnonnull "$Refslist"
  626. then
  627. # mark all remote refs with ^<sha-1> (if sha-1 exists locally)
  628. r_revlist=$(xfeed "$Refslist" cut -f 1 -d ' ' |
  629. safe_git_rev_parse | sed -e 's/^\(.\)/^&/')
  630. fi
  631. while IFS=: read -r src_ dst_ # << +src:dst
  632. do
  633. src_=${src_#+}
  634. filter_to ! @Refslist "$Hex40 $dst_" "$Refslist"
  635. if isnonnull "$src_"
  636. then
  637. append_to @r_revlist "$src_"
  638. obj_=$(xfeed "$src_" safe_git_rev_parse)
  639. append_to @Refslist "$obj_ $dst_"
  640. fi
  641. done <<EOF
  642. $1
  643. EOF
  644. tmp_encrypted=$(tempname packP)
  645. tmp_objlist=$(tempname objlP)
  646. {
  647. xfeed "$r_revlist" git rev-list --objects --stdin --
  648. repack_if_needed @r_pack_delete
  649. } > "$tmp_objlist"
  650. # Only send pack if we have any objects to send
  651. if [ -s "$tmp_objlist" ]
  652. then
  653. key_=$(genkey "$Packkey_bytes")
  654. pack_id=$(\
  655. {
  656. GIT_ALTERNATE_OBJECT_DIRECTORIES=$Localdir \
  657. git pack-objects --stdout < "$tmp_objlist" ||
  658. echo_kill "git pack-objects failed!"
  659. } | {
  660. ENCRYPT "$key_" ||
  661. echo_kill "gpg -c failed!"
  662. } | tee "$tmp_encrypted" | gpg_hash "$Hashtype")
  663. append_to @Packlist "pack :${Hashtype}:$pack_id $key_"
  664. if isnonnull "$r_pack_delete"
  665. then
  666. append_to @Keeplist "keep :${Hashtype}:$pack_id 1"
  667. fi
  668. fi
  669. # Generate manifest
  670. echo_info "Encrypting to: $Recipients"
  671. echo_info "Requesting manifest signature"
  672. tmp_manifest=$(tempname maniP)
  673. PRIVENCRYPT "$Recipients" > "$tmp_manifest" <<EOF
  674. $Refslist
  675. $Packlist
  676. $Keeplist
  677. repo $Repoid
  678. $Extnlist
  679. EOF
  680. # Upload pack
  681. if [ -s "$tmp_objlist" ]
  682. then
  683. PUT "$URL" "$pack_id" "$tmp_encrypted"
  684. fi
  685. # Upload manifest
  686. PUT "$URL" "$Manifestfile" "$tmp_manifest"
  687. rm -f "$tmp_encrypted"
  688. rm -f "$tmp_objlist"
  689. rm -f "$tmp_manifest"
  690. # Delete packs
  691. if isnonnull "$r_pack_delete"; then
  692. rm -r -f "$Localdir/pack"
  693. REMOVE "$URL" "$(xecho "$r_pack_delete" | \
  694. while IFS=': ' read -r _ _ pack_
  695. do
  696. isnonnull "$pack_" || continue
  697. xecho "$pack_"
  698. done)"
  699. fi
  700. PUT_FINAL "$URL"
  701. # ok all updates
  702. while IFS=: read -r src_ dst_ # << +src:dst
  703. do
  704. echo_git "ok $dst_"
  705. done <<EOF
  706. $1
  707. EOF
  708. echo_git
  709. }
  710. cleanup_tmpfiles()
  711. {
  712. (set +f; rm -f "$Localdir"/tmp_*".$$" >&2)
  713. }
  714. # handle git-remote-helpers protocol
  715. gcrypt_main_loop()
  716. {
  717. local input_= input_inner= r_args=
  718. NAME=$1 # Remote name
  719. URL=$2 # Remote URL
  720. trap cleanup_tmpfiles EXIT
  721. trap 'exit 1' 1 2 3 15
  722. mkdir -p "$Localdir"
  723. cleanup_tmpfiles
  724. echo_info "Development version -- Repository format MAY CHANGE"
  725. while read input_
  726. do
  727. case "$input_" in
  728. capabilities)
  729. do_capabilities
  730. ;;
  731. list|list\ for-push)
  732. do_list
  733. ;;
  734. fetch\ *)
  735. r_args=${input_##fetch }
  736. while read input_inner
  737. do
  738. case "$input_inner" in
  739. fetch*)
  740. r_args= #ignored
  741. ;;
  742. *)
  743. break
  744. ;;
  745. esac
  746. done
  747. do_fetch "$r_args"
  748. ;;
  749. push\ *)
  750. r_args=${input_##push }
  751. while read input_inner
  752. do
  753. case "$input_inner" in
  754. push\ *)
  755. append_to @r_args "${input_inner#push }"
  756. ;;
  757. *)
  758. break
  759. ;;
  760. esac
  761. done
  762. do_push "$r_args"
  763. ;;
  764. ?*)
  765. echo_die "Unknown input!"
  766. ;;
  767. *)
  768. CLEAN_FINAL "$URL"
  769. exit 0
  770. ;;
  771. esac
  772. done
  773. }
  774. gcrypt_main_loop "$@"