Skip to content
quarry Artifact [af8baf81d7]
Login

Artifact [af8baf81d7]

Artifact af8baf81d7a92732b10864f78d80aa9bfcfa38e5e6fe78d45d3ab17f6717290f:


package main

// githubabout.go: a repository imported from GitHub starts with GitHub's
// description, homepage and topics, so a fresh mirror or import does not
// read as bare beside the repository it came from. Copied once, at the
// end of the import, into whatever is still empty: a field somebody set
// when creating the repository is theirs, and after the import every
// field is the repository's own to edit (dmestas, 2026-10-31).

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// githubAboutTimeout bounds the one API call; a slow GitHub costs the
// import nothing but the copy.
const githubAboutTimeout = 15 * time.Second

// githubAbout is the part of GitHub's repository object copied.
type githubAbout struct {
	Description string   `json:"description"`
	Homepage    string   `json:"homepage"`
	Topics      []string `json:"topics"`
}

// githubRepoPath is owner/repo for a GitHub address in any of the forms a
// person pastes: https, ssh:// or [email protected]:, with or without .git.
// "" for any other host.
func githubRepoPath(remote string) string {
	s := strings.TrimSpace(remote)
	switch {
	case strings.HasPrefix(s, "[email protected]:"):
		s = strings.TrimPrefix(s, "[email protected]:")
	default:
		u, err := url.Parse(s)
		if err != nil || !strings.EqualFold(u.Hostname(), "github.com") {
			return ""
		}
		s = u.Path
	}
	s = strings.TrimSuffix(strings.Trim(s, "/"), ".git")
	if parts := strings.Split(s, "/"); len(parts) != 2 || parts[0] == "" || parts[1] == "" {
		return ""
	}
	return s
}

// copyGitHubAbout fills name's empty description, homepage and topics
// from the GitHub repository at clone. Every failure only logs: the
// repository is already imported and served, and this is decoration.
func (rg *registrar) copyGitHubAbout(name, clone, token string) {
	if rg.odb == nil {
		return
	}
	path := githubRepoPath(clone)
	if path == "" {
		return
	}
	base := rg.githubAPI
	if base == "" {
		base = "https://api.github.com"
	}
	ctx, cancel := context.WithTimeout(context.Background(), githubAboutTimeout)
	defer cancel()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, base+"/repos/"+path, nil)
	if err != nil {
		return
	}
	req.Header.Set("Accept", "application/vnd.github+json")
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Printf("import %s: GitHub's description: %v", name, err)
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		log.Printf("import %s: GitHub's description: %s", name, resp.Status)
		return
	}
	var about githubAbout
	if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&about); err != nil {
		log.Printf("import %s: GitHub's description: %v", name, err)
		return
	}
	if err := rg.fillAbout(name, about); err != nil {
		log.Printf("import %s: copying GitHub's description: %v", name, err)
	}
}

// fillAbout writes about's fields into name's empty ones, under the rules
// repo.update keeps: one line of description, an http(s) homepage, at most
// twenty topics of at most forty bytes.
func (rg *registrar) fillAbout(name string, about githubAbout) error {
	row, found, err := rg.odb.RepoByName(name)
	if err != nil || !found {
		return err
	}
	if d := strings.Join(strings.Fields(about.Description), " "); row.Description == "" && d != "" {
		if err := rg.odb.SetRepoDescription(name, d); err != nil {
			return err
		}
	}
	if h := strings.TrimSpace(about.Homepage); row.Homepage == "" && h != "" {
		if u, perr := url.Parse(h); perr == nil && (u.Scheme == "http" || u.Scheme == "https") {
			if err := rg.odb.SetRepoHomepage(name, h); err != nil {
				return err
			}
		}
	}
	if row.Topics == "" && len(about.Topics) > 0 {
		var keep []string
		for _, t := range about.Topics {
			if t = strings.TrimSpace(t); t != "" && len(t) <= 40 && len(keep) < 20 {
				keep = append(keep, t)
			}
		}
		if len(keep) > 0 {
			if err := rg.odb.SetRepoTopics(name, strings.Join(keep, " ")); err != nil {
				return fmt.Errorf("topics: %w", err)
			}
		}
	}
	return nil
}