Download
77 lines 2 KiBGoModified 2026-10-28 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
// Package mirror knows about a repository's mirrors, which the
// post-receive hook pushes to: where a mirror URL reaches over ssh, and
// how the last push went.
package mirror

import (
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// FailedFile is the file in a repository where the hook keeps the
// failures of its last mirror push, a line each, and removes it when a
// push to every mirror works.
const FailedFile = "repo-render-mirror.failed"

// Failures are the failures of the last mirror push of the repository at
// gitDir, if it had any.
func Failures(gitDir string) []string {
	b, err := os.ReadFile(filepath.Join(gitDir, FailedFile))
	if err != nil {
		return nil
	}
	var lines []string
	for l := range strings.Lines(string(b)) {
		if l = strings.TrimSpace(l); l != "" {
			lines = append(lines, l)
		}
	}
	return lines
}

// Failed reports whether the last push to the mirror at url failed, by
// failures.
func Failed(failures []string, url string) bool {
	for _, f := range failures {
		if strings.Contains(f, " "+url+" ") {
			return true
		}
	}
	return false
}

var hostRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`)

// Host is the host and port a mirror URL reaches over ssh, if it does:
// user@host:path, or ssh://user@host:port/path. A local path, or a URL
// another way, has none.
func Host(m string) (host, port string) {
	if strings.Contains(m, "://") {
		u, err := url.Parse(m)
		if err != nil || (u.Scheme != "ssh" && u.Scheme != "git+ssh" && u.Scheme != "ssh+git") {
			return "", ""
		}
		host, port = u.Hostname(), u.Port()
	} else if i := strings.IndexByte(m, ':'); i > 0 && !strings.Contains(m[:i], "/") {
		host = m[:i]
		if j := strings.LastIndexByte(host, '@'); j >= 0 {
			host = host[j+1:]
		}
	}
	if !hostRE.MatchString(host) {
		return "", ""
	}
	return host, port
}

// Entry is a host as known_hosts names it: host, or [host]:port for a
// port other than 22.
func Entry(host, port string) string {
	if port == "" || port == "22" {
		return host
	}
	return "[" + host + "]:" + port
}