Skip to content
go-libfossil Documentation
Login

Documentation


title: First sync

weight: 40

Two repositories exchange check-ins with Fossil's sync protocol over HTTP. libfossil repo serve is a sync server; Clone, Repo.Sync and Repo.Pull are the client. Either side can be stock fossil. Sync explains the protocol.

Serve a repository

libfossil -R notes.fossil repo serve
serving /home/ana/notes.fossil on http://127.0.0.1:8080 (Ctrl-C to stop)

--addr changes the bind address. The server answers sync requests only; it has no web interface.

An unauthenticated client gets the capabilities of the nobody user, which a new repository sets as fossil new does: clone and read, no check-in. To let a client push, give a user the check-in capability i and a password:

libfossil -R notes.fossil repo user add --cap=i bo
libfossil -R notes.fossil repo user passwd bo

Both commands print the user's generated password.

Clone

From the command line, with either tool:

libfossil repo clone http://127.0.0.1:8080 copy.fossil
fossil clone http://127.0.0.1:8080 copy2.fossil
Cloned into copy.fossil
  Rounds:       2
  Blobs:        2
  Project-code: ec1581be7066e95dd3b9ee14b91ab3f258221ce4

repo clone also clones part of a repository: --cone by path and --horizon by age. See Sparse cones and Horizons.

Clone, commit, push and pull from Go

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/danmestas/go-libfossil"
	_ "github.com/danmestas/go-libfossil/db/driver/modernc"
)

func main() {
	ctx := context.Background()
	url := "http://127.0.0.1:8080"

	r, res, err := libfossil.Clone(ctx, "copy.fossil", libfossil.NewHTTPTransport(url), libfossil.CloneOpts{})
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close()
	fmt.Println("cloned", res.BlobsRecvd, "blobs in", res.Rounds, "rounds")

	tip, err := r.BranchTip("trunk")
	if err != nil {
		log.Fatal(err)
	}
	if _, _, err := r.Commit(libfossil.CommitOpts{
		Files:    []libfossil.FileToCommit{{Name: "TODO.md", Content: []byte("- ship\n")}},
		Comment:  "add a todo",
		User:     "bo",
		ParentID: tip,
	}); err != nil {
		log.Fatal(err)
	}

	pushed, err := r.Sync(ctx, libfossil.NewHTTPTransport(url), libfossil.SyncOpts{
		Push: true, Pull: true, User: "bo", Password: os.Getenv("NOTES_PASSWORD"),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("sent", pushed.FilesSent, "received", pushed.FilesRecvd)

	pulled, err := r.Pull(ctx, url, libfossil.PullOpts{})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("pull rounds", pulled.Rounds)
}
NOTES_PASSWORD=... go run .
cloned 2 blobs in 2 rounds
sent 2 received 0
pull rounds 1

Clone creates the file and fails if it exists. Sync with Push and Pull runs one session in both directions until neither side has anything the other lacks; Pull is a pull-only shorthand that takes a URL. Without User, a session runs as nobody. Pass a context with a deadline in a service: a stalled peer otherwise holds the call open.

The commit made in the clone went to the origin:

libfossil -R notes.fossil repo timeline
fb5af4c078  2026-10-23 21:02  bo  add a todo
ee68ff62c5  2026-10-23 21:02  ana  first notes

Next