[mike@mwxm4]

This commit is contained in:
2026-08-11 11:40:32 +02:00
parent 113527f220
commit 39f5b48d94
14 changed files with 719 additions and 39 deletions
+69 -9
View File
@@ -238,18 +238,64 @@ func parsePushRemoteArgs(args string) (names []string, description string) {
return names, strings.Join(fields[i:], " ")
}
// remoteHost reduces a configured url — or whatever a user typed in its place —
// to its bare host: scheme, credentials, port and path removed.
func remoteHost(s string) string {
s = strings.TrimSpace(s)
if i := strings.Index(s, "://"); i >= 0 {
s = s[i+3:]
}
if i := strings.IndexByte(s, '@'); i >= 0 { // user[:pass]@host
s = s[i+1:]
}
if i := strings.IndexAny(s, "/:"); i >= 0 {
s = s[:i]
}
return s
}
// remoteMatches reports whether sel picks target t. A target answers both to its
// configured name and to the host of its url: the name is what the config calls
// the server, the host is what the user sees in the browser, and `@gitea` should
// not be the only way to say `git.example.com`.
func remoteMatches(t RemoteTarget, sel string) bool {
sel = strings.TrimPrefix(strings.TrimSpace(sel), "@")
if sel == "" {
return false
}
if strings.EqualFold(t.Name, sel) {
return true
}
h := remoteHost(t.URL)
return h != "" && strings.EqualFold(h, remoteHost(sel))
}
// activeRemotes keeps the targets that take part when a command was given no
// target of its own (see RemoteTarget.isActive).
func activeRemotes(all []RemoteTarget) []RemoteTarget {
out := make([]RemoteTarget, 0, len(all))
for _, t := range all {
if t.isActive() {
out = append(out, t)
}
}
return out
}
// pickRemotes narrows all to the explicitly requested names, complaining about
// any that are not configured. With no names given, all targets are used.
// any that are not configured. With no names given the active targets are used
// — `active = false` takes a server off that automatic path, and naming it is
// what puts it back on.
func pickRemotes(all []RemoteTarget, names []string) []RemoteTarget {
if len(names) == 0 {
return all
return activeRemotes(all)
}
var out []RemoteTarget
seen := map[string]bool{}
for _, n := range names {
found := false
for _, t := range all {
if !strings.EqualFold(t.Name, n) {
if !remoteMatches(t, n) {
continue
}
if !seen[t.Name] { // `@hub @hub` must not push twice
@@ -266,8 +312,24 @@ func pickRemotes(all []RemoteTarget, names []string) []RemoteTarget {
return out
}
// reportNoTargets explains an empty selection. There are three ways to end up
// with nothing to push to, and they need different answers: nothing configured
// at all, everything configured switched to `active = false`, or a name nobody
// recognised — which pickRemotes has already complained about by itself.
func reportNoTargets(cmd string, configured []RemoteTarget, names []string) {
switch {
case len(names) > 0:
return
case len(configured) > 0:
errorln("every configured remote is inactive — name one, e.g. '" +
cmd + " @" + configured[0].Name + "'")
default:
errorln(cmd + " needs a 'remote.<name>.url' and 'remote.<name>.key' in " + configFile())
}
}
// handlePushRemote implements `pushremote [@name ...] [description]`. Without
// a @name it mirrors to every configured target; the description, if given, is
// a @name it mirrors to every active target; the description, if given, is
// set on the repository when it is created.
func handlePushRemote(args string) {
if !requireRepo() {
@@ -275,15 +337,13 @@ func handlePushRemote(args string) {
}
names, description := parsePushRemoteArgs(args)
targets, incomplete := cfg.mirrorTargets()
configured, incomplete := cfg.mirrorTargets()
for _, n := range incomplete {
errorln("remote " + n + ": url or key missing — skipped")
}
targets = pickRemotes(targets, names)
targets := pickRemotes(configured, names)
if len(targets) == 0 {
if len(names) == 0 { // an unknown @name already reported itself
errorln("pushremote needs a 'remote.<name>.url' and 'remote.<name>.key' in " + configFile())
}
reportNoTargets("pushremote", configured, names)
return
}