4f04d2d43b
Introduces gcrypt.participants "simple" mode which encrypts to self, and accepts any valid signature by default. No configuration needed for private repositories. We also adds remote.<name>.gcrypt-participants to configure this per remote.
774 lines
17 KiB
Bash
Executable file
774 lines
17 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
# git-remote-gcrypt
|
|
# Copyright 2013 by Ulrik
|
|
# License: GPLv2 or any later version, see http://www.gnu.org/licenses/
|
|
#
|
|
# See README
|
|
|
|
#set -x
|
|
set -e
|
|
|
|
Did_find_repo= # yes for connected, no for no repo
|
|
Localdir="${GIT_DIR:=.git}/remote-gcrypt"
|
|
export GITCEPTION="${GITCEPTION:-}+" # Reuse $Gref except when stacked
|
|
Gref="refs/gcrypt/gitception$GITCEPTION"
|
|
Gref_rbranch="refs/heads/master"
|
|
Repoid=
|
|
Packkey_bytes=33 # 33 random bytes for passphrase, still compatible if changed
|
|
Hashtype=SHA256 # SHA512 SHA384 SHA256 SHA224 supported.
|
|
Packpat="pack :*:"
|
|
Manifestfile=91bd0c092128cf2e60e1a608c31e92caf1f9c1595f83f2890ef17c0e4881aa0a
|
|
Urlfrag=
|
|
|
|
Branchlist=
|
|
Packlist=
|
|
Keeplist=
|
|
Extension_list=
|
|
Repack_limit=25
|
|
Packlist_delete=
|
|
|
|
Recipients=
|
|
Signers=
|
|
Goodsig=
|
|
|
|
# compat/utility functions
|
|
xecho()
|
|
{
|
|
cat <<EOF
|
|
$@
|
|
EOF
|
|
}
|
|
xecho_n() { xecho "$@" | tr -d \\n ; } # kill newlines
|
|
echo_git() { xecho "$@" ; } # Code clarity
|
|
echo_info() { xecho "gcrypt:" "$@" >&2; }
|
|
echo_die() { echo_info "$@" ; exit 1; }
|
|
|
|
isnull() { case "$1" in "") return 0;; *) return 1;; esac; }
|
|
isnonnull() { ! isnull "$1"; }
|
|
iseq() { case "$1" in "$2") return 0;; *) return 1;; esac; }
|
|
isnoteq() { ! iseq "$@"; }
|
|
|
|
# Append $2 to $1 with a newline separator
|
|
append() { isnull "$1" || xecho "$1" && xecho "$2"; }
|
|
isurl() { isnull "${2%%$1://*}"; }
|
|
islocalrepo() { isnull "${1##/*}" && [ ! -e "$1/HEAD" ]; }
|
|
|
|
xgrep() { command grep "$@" || : ; }
|
|
sort_C() { LC_ALL=C command sort "$@"; }
|
|
sort_stable_k2()
|
|
{
|
|
awk '{ printf("%08d\t%s\n", NR, $0) }' | sort_C -k 3,3 -k 1,1 |cut -f 2-
|
|
}
|
|
|
|
tac() { sed '1!G;h;$!d'; }
|
|
|
|
# Split $1 into $prefix_:$suffix_
|
|
splitcolon()
|
|
{
|
|
prefix_=${1%%:*}
|
|
suffix_=${1#*:}
|
|
}
|
|
repoidstr() { xecho "repo $Repoid"; }
|
|
|
|
## gitception part
|
|
# Fetch giturl $1, file $2
|
|
gitception_get()
|
|
{
|
|
# Take care to preserve FETCH_HEAD
|
|
local ret_=: obj_id= f_head="$GIT_DIR/FETCH_HEAD"
|
|
[ -e "$f_head" ] && command mv -f "$f_head" "$f_head.$$~" || :
|
|
git fetch -q -f "$1" "refs/heads/${Urlfrag:-master}:$Gref" 2>/dev/tty >/dev/null &&
|
|
obj_id="$(git ls-tree "$Gref" | xgrep -E '\b'"$2"'$' | awk '{print $3}')" &&
|
|
isnonnull "$obj_id" && git cat-file blob "$obj_id" && ret_=: ||
|
|
{ ret_=false && : ; }
|
|
[ -e "$f_head.$$~" ] && command mv -f "$f_head.$$~" "$f_head" || :
|
|
$ret_
|
|
}
|
|
|
|
anon_commit()
|
|
{
|
|
GIT_AUTHOR_NAME="root" GIT_AUTHOR_EMAIL="root@localhost" \
|
|
GIT_AUTHOR_DATE="1356994801 -0400" GIT_COMMITTER_NAME="root" \
|
|
GIT_COMMITTER_EMAIL="root@localhost" \
|
|
GIT_COMMITTER_DATE="1356994801 -0400" \
|
|
git commit-tree "$@" <<EOF
|
|
Initial commit
|
|
EOF
|
|
}
|
|
|
|
# Get 'tree' from $1, change file $2 to obj id $3
|
|
update_tree()
|
|
{
|
|
local tab_=" "
|
|
# $2 is a filename from the repo format
|
|
(git ls-tree "$1" | xgrep -v -E '\b'"$2"'$';
|
|
xecho "100644 blob $3$tab_$2") | git mktree
|
|
}
|
|
|
|
# Put giturl $1, file $2
|
|
# depends on previous GET to set $Gref and depends on PUT_FINAL later
|
|
gitception_put()
|
|
{
|
|
local obj_id= tree_id= commit_id=
|
|
obj_id=$(git hash-object -w --stdin) &&
|
|
tree_id=$(update_tree "$Gref" "$2" "$obj_id") &&
|
|
commit_id=$(anon_commit "$tree_id") &&
|
|
git update-ref "$Gref" "$commit_id"
|
|
}
|
|
|
|
# Remove giturl $1, file $2
|
|
# depends on previous GET like put
|
|
gitception_remove()
|
|
{
|
|
local tree_id= commit_id= tab_=" "
|
|
# $2 is a filename from the repo format
|
|
tree_id=$(git ls-tree "$Gref" | xgrep -v -E '\b'"$2"'$' | git mktree) &&
|
|
commit_id=$(anon_commit "$tree_id") &&
|
|
git update-ref "$Gref" "$commit_id"
|
|
}
|
|
|
|
gitception_new_repo()
|
|
{
|
|
local empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
|
# get any file to update Gref, and if it's not updated we create empty
|
|
git update-ref -d "$Gref" || :
|
|
gitception_get "$1" "x" || :
|
|
git rev-parse -q --verify "$Gref" >/dev/null && return 0 ||
|
|
commit_id=$(anon_commit "$empty_tree") &&
|
|
git update-ref "$Gref" "$commit_id"
|
|
}
|
|
## end gitception
|
|
|
|
# Fetch repo $1, file $2, tmpfile in $3
|
|
GET()
|
|
{
|
|
if isurl sftp "$1"
|
|
then
|
|
(exec 0>&-; curl -s -S -k "$1/$2") > "$3"
|
|
elif isurl rsync "$1"
|
|
then
|
|
(exec 0>&-; rsync -I -W "${1#rsync://}"/"$2" "$3" >&2)
|
|
elif islocalrepo "$1"
|
|
then
|
|
cat "$1/$2" > "$3"
|
|
else
|
|
gitception_get "${1#gitception://}" "$2" > "$3"
|
|
fi
|
|
}
|
|
|
|
# Put repo $1, file $2 or fail, tmpfile in $3
|
|
PUT()
|
|
{
|
|
if isurl sftp "$1"
|
|
then
|
|
curl -s -S -k --ftp-create-dirs -T "$3" "$1/$2"
|
|
elif isurl rsync "$1"
|
|
then
|
|
rsync -I -W "$3" "${1#rsync://}"/"$2" >&2
|
|
elif islocalrepo "$1"
|
|
then
|
|
cat > "$1/$2" < "$3"
|
|
else
|
|
gitception_put "${1#gitception://}" "$2" < "$3"
|
|
fi
|
|
}
|
|
|
|
# Put all PUT changes for repo $1 at once
|
|
PUT_FINAL()
|
|
{
|
|
if isurl sftp "$1" || islocalrepo "$1" || isurl rsync "$1"
|
|
then
|
|
:
|
|
else
|
|
git push --quiet -f "${1#gitception://}" \
|
|
"$Gref:refs/heads/${Urlfrag:-master}"
|
|
fi
|
|
}
|
|
|
|
# Put directory for repo $1
|
|
PUTREPO()
|
|
{
|
|
if isurl sftp "$1"
|
|
then
|
|
:
|
|
elif isurl rsync "$1"
|
|
then
|
|
rsync -q -r --exclude='*' "$Localdir/" "${1#rsync://}" >&2
|
|
elif islocalrepo "$1"
|
|
then
|
|
mkdir -p "$1"
|
|
else
|
|
gitception_new_repo "${1#gitception://}"
|
|
fi
|
|
}
|
|
|
|
# For repo $1, delete all newline-separated files in $2
|
|
REMOVE()
|
|
{
|
|
local fn_=
|
|
if isurl sftp "$1"
|
|
then
|
|
# FIXME
|
|
echo_info "sftp: Ignore remove request $1/$2"
|
|
elif isurl rsync "$1"
|
|
then
|
|
xecho "$2" | rsync -I -W -v -r --delete --include-from=- \
|
|
--exclude='*' "$Localdir"/ "${1#rsync://}/" >&2
|
|
elif islocalrepo "$1"
|
|
then
|
|
for fn_ in $2; do
|
|
rm -f "$1"/"$fn_"
|
|
done
|
|
else
|
|
for fn_ in $2; do
|
|
gitception_remove "${1#gitception://}" "$fn_"
|
|
done
|
|
fi
|
|
}
|
|
|
|
CLEAN_FINAL()
|
|
{
|
|
if isurl sftp "$1" || islocalrepo "$1" || isurl rsync "$1"
|
|
then
|
|
:
|
|
else
|
|
git update-ref -d "$Gref" || :
|
|
fi
|
|
}
|
|
|
|
addsignkeyparam()
|
|
{
|
|
if isnull "$Conf_signkey"; then
|
|
"$@"
|
|
else
|
|
"$@" -u "$Conf_signkey"
|
|
fi
|
|
}
|
|
|
|
|
|
ENCRYPT()
|
|
{
|
|
gpg --batch --force-mdc --compress-algo none --passphrase-fd 3 -c 3<<EOF
|
|
$1
|
|
EOF
|
|
}
|
|
|
|
DECRYPT()
|
|
{
|
|
gpg -q --batch --no-default-keyring --secret-keyring /dev/null \
|
|
--keyring /dev/null --passphrase-fd 3 -d 3<<EOF
|
|
$1
|
|
EOF
|
|
}
|
|
|
|
# Encrypt to recipients $1
|
|
PRIVENCRYPT()
|
|
{
|
|
addsignkeyparam gpg --compress-algo none -se $1
|
|
}
|
|
|
|
PRIVDECRYPT()
|
|
{
|
|
local status_=
|
|
exec 4>&1 &&
|
|
status_=$(gpg --status-fd 3 -q -d 3>&1 1>&4) &&
|
|
xecho "$status_" | grep "^\[GNUPG:\] ENC_TO " >/dev/null &&
|
|
(xecho "$status_" | grep -e "$Goodsig" >/dev/null || {
|
|
echo_info "Failed to verify manifest signature!" &&
|
|
echo_info "Only accepting signatories: ${Signers:-(none)}" &&
|
|
return 1
|
|
})
|
|
}
|
|
|
|
# Generate $1 random bytes
|
|
genkey()
|
|
{
|
|
gpg --armor --gen-rand 1 "$1"
|
|
}
|
|
|
|
gpg_hash()
|
|
{
|
|
local hash_=
|
|
hash_=$(gpg --with-colons --print-md "$1" | tr A-F a-f)
|
|
hash_=${hash_#:*:}
|
|
xecho "${hash_%:}"
|
|
}
|
|
|
|
pack_hash() { gpg_hash "$Hashtype"; }
|
|
|
|
|
|
# Pass the branch/ref by pipe to git
|
|
safe_git_rev_parse()
|
|
{
|
|
git cat-file --batch-check 2>/dev/null |
|
|
xgrep -v "missing" | cut -f 1 -d ' '
|
|
}
|
|
|
|
make_new_repo()
|
|
{
|
|
local urlid_= fix_config=
|
|
echo_info "Setting up new repository"
|
|
PUTREPO "$URL"
|
|
|
|
# Needed assumption: the same user should have no duplicate Repoid
|
|
Repoid=":${Hashtype}:$(genkey 64 | pack_hash)"
|
|
iseq "${NAME#gcrypt::}" "$URL" || {
|
|
git config "remote.$NAME.gcrypt-id" "$Repoid"
|
|
fix_config=1
|
|
}
|
|
echo_info "Repository ID is $Repoid"
|
|
Extension_list=$(xecho "extn comment")
|
|
#isnull "$fix_config" || echo_info "(configuration for $NAME updated)"
|
|
}
|
|
|
|
|
|
read_config()
|
|
{
|
|
local recp_= key_line= cap_= conf_keyring= conf_part=
|
|
Conf_signkey=$(git config --path user.signingkey || :)
|
|
conf_keyring=$(git config --path gcrypt.keyring || :)
|
|
conf_part=$(git config --get "remote.$NAME.gcrypt-participants" '.+' ||
|
|
git config --get gcrypt.participants '.+' || :)
|
|
|
|
# Figure out which keys we should encrypt to or accept signatures from
|
|
if isnonnull "$conf_keyring" && isnull "$conf_part"
|
|
then
|
|
echo_info "WARNING: Setting gcrypt.keyring is deprecated," \
|
|
"use gcrypt.participants instead."
|
|
conf_part=$(gpg --no-default-keyring --keyring "$conf_keyring" \
|
|
--with-colons --fast-list -k | grep ^pub | cut -f 5 -d :)
|
|
fi
|
|
|
|
if isnull "$conf_part" || iseq "$conf_part" simple
|
|
then
|
|
Signers="(default keyring)"
|
|
Recipients="--throw-keyids --default-recipient-self"
|
|
Goodsig="^\[GNUPG:\] GOODSIG "
|
|
return 0
|
|
fi
|
|
|
|
for recp_ in $conf_part
|
|
do
|
|
key_line=$(gpg --with-colons --fast-list -k "$recp_" | xgrep ^pub)
|
|
keyid_=$(xecho "$key_line" | cut -f 5 -d :)
|
|
|
|
isnonnull "$keyid_" &&
|
|
Signers="$Signers $keyid_" &&
|
|
Goodsig=$(append "$Goodsig" "^\[GNUPG:\] GOODSIG $keyid_") || {
|
|
echo_info "WARNING: Skipping missing key $recp_"
|
|
continue
|
|
}
|
|
# Check 'E'ncrypt capability
|
|
cap_=$(xecho "$key_line" | cut -f 12 -d :)
|
|
iseq "${cap_#*E}" "$cap_" || Recipients="$Recipients -R $keyid_"
|
|
done
|
|
|
|
if isnull "$Recipients"
|
|
then
|
|
echo_info "You have not configured any keys to encrypt to for this repository"
|
|
echo_info "Use ::"
|
|
echo_info " git config gcrypt.participants YOURKEYID"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_connected()
|
|
{
|
|
local manifest_= rcv_repoid= r_name=
|
|
|
|
if isnonnull "$Did_find_repo"
|
|
then
|
|
return
|
|
fi
|
|
Did_find_repo=no
|
|
read_config
|
|
|
|
iseq "${NAME#gcrypt::}" "$URL" || r_name=$NAME
|
|
|
|
# Fixup ssh:// -> rsync://
|
|
if isurl ssh "$URL"; then
|
|
URL="rsync://${URL#ssh://}"
|
|
fi
|
|
|
|
# Find the URL fragment
|
|
Urlfrag=${URL##*"#"}
|
|
isnoteq "$Urlfrag" "$URL" || Urlfrag=
|
|
URL=${URL%"#$Urlfrag"}
|
|
|
|
# manifestfile -- sha224 hash if we can, else the default location
|
|
if isurl sftp "$URL" || islocalrepo "$URL" || isurl rsync "$URL"
|
|
then
|
|
# not for gitception
|
|
isnull "$Urlfrag" || Manifestfile=$(xecho_n "$Urlfrag" | gpg_hash SHA224)
|
|
fi
|
|
|
|
Repoid=
|
|
isnull "$r_name" || {
|
|
Repoid=$(git config "remote.$r_name.gcrypt-id" || :)
|
|
}
|
|
|
|
|
|
TmpManifest_Enc="$Localdir/tmp_manifest.$$"
|
|
GET "$URL" "$Manifestfile" "$TmpManifest_Enc" 2>/dev/null || {
|
|
echo_info "Repository not found: $URL"
|
|
return 0
|
|
}
|
|
|
|
Did_find_repo=yes
|
|
echo_info "Decrypting manifest"
|
|
manifest_=$(PRIVDECRYPT < "$TmpManifest_Enc") &&
|
|
isnonnull "$manifest_" ||
|
|
echo_die "Failed to decrypt manifest!"
|
|
rm -f "$TmpManifest_Enc"
|
|
|
|
Branchlist=$(xecho "$manifest_" | xgrep -E '^[0-9a-f]{40} ')
|
|
Packlist=$(xecho "$manifest_" | xgrep "^pack ")
|
|
Keeplist=$(xecho "$manifest_" | xgrep "^keep ")
|
|
Extension_list=$(xecho "$manifest_" | xgrep "^extn ")
|
|
rcv_repoid=$(xecho "$manifest_" | xgrep "^repo ")
|
|
|
|
rcv_repoid=${rcv_repoid#repo }
|
|
rcv_repoid=${rcv_repoid% *}
|
|
if isnull "$Repoid"
|
|
then
|
|
echo_info "Remote repo ID is $rcv_repoid"
|
|
Repoid=$rcv_repoid
|
|
elif isnoteq "$rcv_repoid" "$Repoid"
|
|
then
|
|
echo_info "WARNING:"
|
|
echo_info "WARNING: Remote repository ID has changed!"
|
|
echo_info "WARNING: to $rcv_repoid"
|
|
echo_info "WARNING:"
|
|
Repoid=$rcv_repoid
|
|
else
|
|
return 0
|
|
fi
|
|
|
|
isnull "$r_name" || {
|
|
git config "remote.$r_name.gcrypt-id" "$rcv_repoid"
|
|
}
|
|
}
|
|
|
|
fetch_decrypt_pack()
|
|
{
|
|
local key_= rcv_id= htype_= pack_= hfunc_=
|
|
splitcolon "${1#pack :}"
|
|
htype_=$prefix_
|
|
pack_=$suffix_
|
|
|
|
if isnoteq "$htype_" SHA256 && isnoteq "$htype_" SHA224 &&
|
|
isnoteq "$htype_" SHA384 && isnoteq "$htype_" SHA512
|
|
then
|
|
echo_die "Packline malformed: $1"
|
|
fi
|
|
GET "$URL" "$pack_" "$TmpPack_Encrypted" &&
|
|
rcv_id=$(gpg_hash "$htype_" < "$TmpPack_Encrypted") &&
|
|
iseq "$rcv_id" "$pack_" ||
|
|
echo_die "Packfile $pack_ does not match digest!"
|
|
key_=$(xecho "$Packlist" | grep "$pack_" | cut -f 3 -d ' ')
|
|
DECRYPT "$key_" < "$TmpPack_Encrypted"
|
|
}
|
|
|
|
# $1 is new pack id $2 key
|
|
# set did_repack=yes if repacked
|
|
repack_if_needed()
|
|
{
|
|
local pack_= packline_= premote_= key_= pkeep_= n_=
|
|
|
|
# $TmpPack_Encrypted set in caller
|
|
|
|
did_repack=no
|
|
isnonnull "$Packlist" || return 0
|
|
|
|
if isnonnull "$GCRYPT_FULL_REPACK"
|
|
then
|
|
Keeplist=
|
|
Repack_limit=1
|
|
fi
|
|
|
|
premote_=$(xecho "$Packlist" | cut -f 1-2 -d ' ')
|
|
pkeep_=$(xecho "$Keeplist" | cut -f 2 -d ' ')
|
|
|
|
if isnull "$pkeep_"; then
|
|
n_=$(xecho "$Packlist" | wc -l)
|
|
else
|
|
n_=$(xecho "$Packlist" | grep -v -e "$pkeep_" | wc -l)
|
|
fi
|
|
if [ $Repack_limit -gt "$n_" ]; then
|
|
return
|
|
fi
|
|
echo_info "Repacking remote $NAME, ..."
|
|
|
|
rm -r -f "$Localdir/pack"
|
|
mkdir -p "$Localdir/pack"
|
|
DECRYPT "$2" < "$TmpPack_Encrypted" |
|
|
git index-pack -v --stdin "$Localdir/pack/${1}.pack" >/dev/null
|
|
|
|
xecho "$premote_" | while read packline_
|
|
do
|
|
isnonnull "$packline_" || continue
|
|
if isnonnull "$pkeep_" &&
|
|
xecho "$packline_" | grep -q -e "$pkeep_"
|
|
then
|
|
continue
|
|
fi
|
|
pack_=${packline_#$Packpat}
|
|
fetch_decrypt_pack "$packline_" |
|
|
git index-pack -v --stdin "$Localdir/pack/${pack_}.pack" >/dev/null
|
|
done
|
|
key_=$(genkey "$Packkey_bytes")
|
|
|
|
git verify-pack -v "$Localdir"/pack/*.idx | grep -E '^[0-9a-f]{40}' |
|
|
cut -f 1 -d ' ' |
|
|
GIT_ALTERNATE_OBJECT_DIRECTORIES=$Localdir \
|
|
git pack-objects --stdout | ENCRYPT "$key_" > "$TmpPack_Encrypted"
|
|
|
|
# Truncate packlist to only the kept packs
|
|
if isnull "$pkeep_"; then
|
|
Packlist_delete=$premote_
|
|
Packlist=
|
|
else
|
|
Packlist_delete=$(xecho "$premote_" | xgrep -v -e "$pkeep_")
|
|
Packlist=$(xecho "$Packlist" | xgrep -e "$pkeep_")
|
|
fi
|
|
|
|
pack_id=$(pack_hash < "$TmpPack_Encrypted")
|
|
Packlist=$(append "$Packlist" "pack :${Hashtype}:$pack_id $key_")
|
|
Keeplist=$(append "$Keeplist" "keep :${Hashtype}:$pack_id 1")
|
|
rm -r -f "$Localdir/pack"
|
|
did_repack=yes
|
|
}
|
|
|
|
do_capabilities()
|
|
{
|
|
echo_git fetch
|
|
echo_git push
|
|
echo_git
|
|
}
|
|
|
|
do_list()
|
|
{
|
|
local obj_id= ref_name= line_=
|
|
ensure_connected
|
|
|
|
xecho "$Branchlist" | while read line_
|
|
do
|
|
isnonnull "$line_" || break
|
|
obj_id=${line_%% *}
|
|
ref_name=${line_##* }
|
|
echo_git "$obj_id" "$ref_name"
|
|
if iseq "$ref_name" "refs/heads/master"
|
|
then
|
|
echo_git "@refs/heads/master HEAD"
|
|
fi
|
|
done
|
|
|
|
# end with blank line
|
|
echo_git
|
|
}
|
|
|
|
do_fetch()
|
|
{
|
|
# The PACK id is the hash of the encrypted git packfile.
|
|
# We only download packs mentioned in the encrypted manifest,
|
|
# and check their digest when received.
|
|
local pack_= packline_= pneed_= phave_= premote_=
|
|
|
|
ensure_connected
|
|
|
|
if isnull "$Packlist"
|
|
then
|
|
echo_git # end with blank line
|
|
return
|
|
fi
|
|
|
|
TmpPack_Encrypted="$Localdir/tmp_pack_ENCRYPTED_.$$"
|
|
|
|
premote_=$(xecho "$Packlist" | cut -f 1-2 -d ' ')
|
|
# The `+` for $GITCEPTION is pointless but we will be safe for stacking
|
|
phave_="$(cat "$Localdir/have_packs+" 2>/dev/null || :)"
|
|
pneed_="$(xecho "$premote_" | xgrep -v -x -e "$phave_")"
|
|
|
|
xecho "$pneed_" | while read packline_
|
|
do
|
|
isnonnull "$packline_" || continue
|
|
fetch_decrypt_pack "$packline_" |
|
|
git index-pack -v --stdin >/dev/null
|
|
# add to local pack list
|
|
xecho "${packline_}" >> "$Localdir/have_packs$GITCEPTION"
|
|
done
|
|
|
|
rm -f "$TmpPack_Encrypted"
|
|
echo_git # end with blank line
|
|
}
|
|
|
|
# do_push PUSHARGS (multiple lines like +src:dst, with both + and src opt.)
|
|
do_push()
|
|
{
|
|
# Security protocol:
|
|
# Each git packfile is encrypted and then named for the encrypted
|
|
# file's hash. The manifest is updated with the pack id.
|
|
# The manifest is encrypted.
|
|
local remote_has= remote_want= prefix_= suffix_= line_= pack_id= key_=
|
|
del_hash=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
|
|
ensure_connected
|
|
|
|
if iseq "$Did_find_repo" "no"
|
|
then
|
|
make_new_repo
|
|
fi
|
|
|
|
if isnonnull "$Branchlist"
|
|
then
|
|
# filter through batch-check to mark only the commits we have
|
|
remote_has=$(xecho "$Branchlist" | cut -f 1 -d ' ' |
|
|
safe_git_rev_parse | sed -e 's/^\(.\)/^&/')
|
|
fi
|
|
|
|
while read line_ # from <<
|
|
do
|
|
# +src:dst -- remove leading + then split at :
|
|
splitcolon "${line_#+}"
|
|
if isnonnull "$prefix_"
|
|
then
|
|
remote_want=$(append "$remote_want" "$prefix_")
|
|
Branchlist=$(append "$Branchlist" \
|
|
"$(xecho "$prefix_" | safe_git_rev_parse) $suffix_")
|
|
else
|
|
# Mark branch for deletion
|
|
Branchlist=$(append "$Branchlist" "$del_hash $suffix_")
|
|
fi
|
|
done <<EOF
|
|
$1
|
|
EOF
|
|
|
|
Branchlist=$(xecho "$Branchlist" | sort_stable_k2 | tac | uniq -s 40 |
|
|
xgrep -v "^$del_hash")
|
|
|
|
TmpPack_Encrypted="$Localdir/tmp_pack_ENCRYPTED_.$$"
|
|
TmpObjlist="$Localdir/tmp_packrevlist.$$"
|
|
key_=$(genkey "$Packkey_bytes")
|
|
|
|
append "$remote_has" "$remote_want" |
|
|
git rev-list --objects --stdin -- |
|
|
tee "$TmpObjlist" |
|
|
git pack-objects --stdout | ENCRYPT "$key_">"$TmpPack_Encrypted"
|
|
# Only send pack if we have any objects to send
|
|
if [ -s "$TmpObjlist" ]
|
|
then
|
|
pack_id=$(pack_hash < "$TmpPack_Encrypted")
|
|
did_repack=
|
|
repack_if_needed "$pack_id" "$key_"
|
|
|
|
if isnoteq "$did_repack" yes
|
|
then
|
|
Packlist=$(append "$Packlist" "pack :${Hashtype}:$pack_id $key_")
|
|
fi
|
|
# else, repack rewrote Packlist
|
|
|
|
fi
|
|
|
|
# Generate manifest
|
|
echo_info "Encrypting to: $Recipients"
|
|
echo_info "Requesting manifest signature"
|
|
|
|
TmpManifest_Enc="$Localdir/tmp_manifest.$$"
|
|
|
|
(xecho "$Branchlist"; xecho "$Packlist"; xecho "$Keeplist";
|
|
repoidstr; xecho "$Extension_list") |
|
|
PRIVENCRYPT "$Recipients" > "$TmpManifest_Enc"
|
|
|
|
# Upload pack
|
|
if [ -s "$TmpObjlist" ]
|
|
then
|
|
PUT "$URL" "$pack_id" "$TmpPack_Encrypted"
|
|
fi
|
|
rm -f "$TmpPack_Encrypted"
|
|
rm -f "$TmpObjlist"
|
|
|
|
# Upload manifest
|
|
PUT "$URL" "$Manifestfile" "$TmpManifest_Enc"
|
|
|
|
# Delete packs
|
|
if isnonnull "$Packlist_delete"; then
|
|
REMOVE "$URL" "$(xecho "$Packlist_delete" | while read packline_
|
|
do
|
|
isnonnull "$packline_" || continue
|
|
pack_=${packline_#$Packpat}
|
|
xecho "$pack_"
|
|
done)"
|
|
fi
|
|
|
|
PUT_FINAL "$URL"
|
|
|
|
rm -f "$TmpManifest_Enc"
|
|
|
|
# ok all updates
|
|
xecho "$1" | while read line_
|
|
do
|
|
# +src:dst -- remove leading + then split at :
|
|
splitcolon "${line_#+}"
|
|
echo_git "ok $suffix_"
|
|
done
|
|
|
|
echo_git
|
|
}
|
|
|
|
NAME=$1 # Remote name
|
|
URL=$2 # Remote URL
|
|
|
|
mkdir -p "$Localdir"
|
|
|
|
trap 'rm -f "$Localdir/tmp_"*".$$"' EXIT 1 2 3 15
|
|
echo_info "Development version -- Repository format WILL CHANGE in the future"
|
|
|
|
while read Input
|
|
do
|
|
case "$Input" in
|
|
capabilities)
|
|
do_capabilities
|
|
;;
|
|
list|list\ for-push)
|
|
do_list
|
|
;;
|
|
fetch\ *)
|
|
args_="${Input##fetch }"
|
|
while read InputX
|
|
do
|
|
case "$InputX" in
|
|
fetch*)
|
|
args_= #ignored
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
do_fetch "$args_"
|
|
;;
|
|
push\ *)
|
|
args_="${Input##push }"
|
|
while read InputX
|
|
do
|
|
case "$InputX" in
|
|
push\ *)
|
|
args_=$(append "$args_" "${InputX#push }")
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
do_push "$args_"
|
|
;;
|
|
?*)
|
|
echo_die "Unknown input!"
|
|
;;
|
|
*)
|
|
CLEAN_FINAL "$URL"
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|