package zitcli
import (
"bytes"
"context"
"fmt"
"os"
"strings"
"craftdesign.group/quarry/internal/lease"
)
// fossilPushArgs is the whole command line of a Fossil mirror's push.
// The password is not in it: argv is readable by every process on the
// machine, so it travels in ZIT_PASSWORD (see FossilPush).
//
// --once authenticates this push and saves nothing: without it zit
// keeps the derived login in the store and a later pull or push to the
// same remote reuses it, a credential nobody here can see or revoke.
//
// resolve pins the address zit connects to, one "HOST:PORT:ADDR" per
// entry (zit's --resolve): with any given zit never looks a name up, so
// the address the caller vetted is the one dialled, and a redirect to
// another host or port is refused by zit itself.
func fossilPushArgs(remote, user string, resolve []string) []string {
args := []string{"push", remote, "--user", user, "--once"}
for _, r := range resolve {
args = append(args, "--resolve", r)
}
return args
}
// FossilPush pushes everything the store holds (check-ins, wiki,
// tickets, forum, every branch) to the Fossil server at remote over
// Fossil's sync protocol, logging in as user, connecting to the addresses resolve pins, with env added to the
// child's environment: the caller puts ZIT_PASSWORD there. It answers
// zit's output either way, because on a refused push that output is the
// only account of why. Fossil sync only adds, so nothing on the remote
// is ever discarded.
//
// Runs in the store directory: zit push, like pull, refuses -R. Under
// the store's lease like the other unattended writes, so a node that
// loses the lease stops pushing; ctx bounds it.
func (c *Client) FossilPush(tok lease.Token, ctx context.Context, remote, user string, resolve, env []string) (string, error) {
if err := tok.Err(); err != nil {
return "", err
}
ctx, stop := underLease(ctx, tok)
defer stop()
args := fossilPushArgs(remote, user, resolve)
if err := checkArgs(args); err != nil {
return "", err
}
cmd, done := spawnUnattended(ctx, repoName(c.repo), "fossil-mirror", args...)
defer done()
if cmd.Err != nil {
return "", cmd.Err
}
cmd.Dir = c.repo
cmd.Env = append(os.Environ(), env...)
OwnGroup(cmd)
cmd.WaitDelay = waitDelay
var out bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &out
if err := cmd.Run(); err != nil {
return strings.TrimSpace(out.String()), fmt.Errorf("zit push: %w", err)
}
return strings.TrimSpace(out.String()), nil
}