Merge pull request #102 from entireio/paul/ua-service-identity · Entire

Merge pull request #102 from entireio/paul/ua-service-identity

5eb8cb0→main·

toothbrush·1w ago·7 files·+196 added/-12 removed

Add SetIdentity for embedder User-Agent attribution; derive default version from build info

Changes

Added

Changed

Removed

package gitsync

import (
    "strings"
    "entire.io/entire/git-sync/internal/useragent"
)

// SetIdentity names the embedding service in every request git-sync makes:
// the HTTP User-Agent and the git-protocol "agent=" capability become
// "<service>/<version> git-sync/<git-sync-version> go-git/<go-git-version>"
// (non-git provider requests carry the same string without the go-git
// token). git-sync's own version is reported regardless; SetIdentity adds
// the service's identity in front so server operators can attribute traffic
// to the service, not just the library.
//
// The identity is process-wide and read on every request: call SetIdentity
// once at startup, before issuing requests from any Client. An empty
// service removes the identity; an empty version advertises the bare
// service name. Whitespace within either value is collapsed to "-" so the
// result stays a single User-Agent product token.
func SetIdentity(service, version string) {
    service = sanitizeToken(service)
    if service == "" {
        useragent.Identity = ""
        return
    }
    if version = sanitizeToken(version); version != "" {
        service += "/" + version
    }
    useragent.Identity = service
}

func sanitizeToken(s string) string {
    return strings.Join(strings.Fields(s), "-")
}
package gitsync

import (
    "testing"
    "entire.io/entire/git-sync/internal/useragent"
    "github.com/go-git/go-git/v6/plumbing/protocol/capability"
)

func TestSetIdentity(t *testing.T) {
    // Not parallel — mutates process-wide user-agent state.
    orig := useragent.Identity
    t.Cleanup(func() { useragent.Identity = orig })

tests := []struct {
        name             string
        service, version string
        wantIdentity     string
    }{
        {"service and version", "mirror-worker", "abc1234", "mirror-worker/abc1234"},
        {"service only", "mirror-worker", "", "mirror-worker"},
        {"empty service clears", "", "abc1234", ""},
        {"whitespace collapsed", "mirror worker", "v1 2", "mirror-worker/v1-2"},
        {"blank service clears", "   ", "abc1234", ""},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            SetIdentity(tt.service, tt.version)
            if useragent.Identity != tt.wantIdentity {
                t.Errorf("SetIdentity(%q, %q) → Identity = %q, want %q",
                    t.service, tt.version, useragent.Identity, tt.wantIdentity)
            }
        })
    }
}

func TestModuleVersionInTestBinary(t *testing.T) {
    t.Parallel()

// In this repo's own test binary git-sync is the main module with no
    // stamped version, so the build-info lookup must fall back to "dev"
    // rather than leaking "(devel)" or an empty string into the UA.
    if got := moduleVersion(); got != "dev" {
        t.Errorf("moduleVersion() in test binary = %q, want %q", got, "dev")
    }
}