shithub: furgit

ref: ba8c85ed2456c59269214f6e4f1203537fb3f6d4
dir: /packfile/ingest/finalize.go/

View raw version
package ingest

import (
	"errors"
	"fmt"
	"io/fs"
	"strings"

	"codeberg.org/lindenii/furgit/internal/intconv"
)

// finalizeArtifacts links temporary files to final names and returns Result.
func finalizeArtifacts(state *ingestState) (Result, error) {
	base := "pack-" + state.packHash.String()
	packFinal := base + ".pack"
	idxFinal := base + ".idx"

	revFinal := ""
	if state.opts.WriteRev {
		revFinal = base + ".rev"
	}

	err := linkTempToFinal(state, state.packTmpName, packFinal)
	if err != nil {
		return Result{}, err
	}

	err = linkTempToFinal(state, state.idxTmpName, idxFinal)
	if err != nil {
		return Result{}, err
	}

	if state.opts.WriteRev {
		err := linkTempToFinal(state, state.revTmpName, revFinal)
		if err != nil {
			return Result{}, err
		}
	}

	objectCount, err := intconv.IntToUint32(len(state.records))
	if err != nil {
		return Result{}, err
	}

	return Result{
		PackName:    packFinal,
		IdxName:     idxFinal,
		RevName:     revFinal,
		PackHash:    state.packHash,
		ObjectCount: objectCount,
		ThinFixed:   state.thinFixed,
	}, nil
}

// rollbackTemporaryArtifacts removes temporary files after failure.
func rollbackTemporaryArtifacts(state *ingestState) {
	if state.packTmpName != "" {
		_ = state.destination.Remove(state.packTmpName)
	}

	if state.idxTmpName != "" {
		_ = state.destination.Remove(state.idxTmpName)
	}

	if state.revTmpName != "" {
		_ = state.destination.Remove(state.revTmpName)
	}
}

// linkTempToFinal hard-links tmp to final, tolerating existing final paths.
func linkTempToFinal(state *ingestState, tmp, final string) error {
	if tmp == "" || final == "" {
		return fmt.Errorf("packfile/ingest: invalid finalize names tmp=%q final=%q", tmp, final)
	}

	if strings.Contains(final, "/") {
		return fmt.Errorf("packfile/ingest: final name must be leaf: %q", final)
	}

	err := state.destination.Link(tmp, final)
	if err == nil {
		_ = state.destination.Remove(tmp)

		return nil
	}

	if errors.Is(err, fs.ErrExist) {
		_ = state.destination.Remove(tmp)

		return nil
	}

	return err
}