Files
homarr/tools/homarr-dev/internal/run/prepare.go
T
+12 fcd7c979df feat!: release v2 (#6545)
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
Co-authored-by: Danura30082 <danura30082+github@gmail.com>
Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com>
Co-authored-by: ajnart <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
Co-authored-by: chenjun <chenjun@foxmail.com>
Co-authored-by: Wikibear <wikibear@users.noreply.github.com>
Co-authored-by: Bergasha <bergasha@gmail.com>
Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com>
Co-authored-by: Lucas Araujo <lucas.lra@gmail.com>
Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com>
Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social>
Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com>
Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com>
Co-authored-by: thrawnx <tight.bulb9384@fastmail.com>
Co-authored-by: Chris <58918656+nordada@users.noreply.github.com>
Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com>
Co-authored-by: Yanis <naamaneyanis@gmail.com>
Co-authored-by: Wikibear <me@sej.de>
Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
2026-10-02 21:00:14 +02:00

129 lines
4.3 KiB
Go

package run
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/docker"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/gh"
)
// Cleanup releases a temporary checkout. It is always safe to call.
type Cleanup func()
func noCleanup() {}
// CurrentBuildOptions describes a build of the checkout that contains
// directory, recording the source and revision on the image so it can be
// rebuilt later without the user remembering where it came from.
func CurrentBuildOptions(ctx context.Context, tag, directory string) (docker.BuildOptions, error) {
root, revision, err := checkoutDetails(ctx, directory)
if err != nil {
return docker.BuildOptions{}, err
}
return docker.BuildOptions{Context: root, Tag: tag, Source: root, Revision: revision}, nil
}
// PRBuildOptions clones a pull request into a temporary checkout and describes
// a build of it. The returned Cleanup must run once the build has finished.
//
// Progress is reported through log rather than written to stdout, because the
// caller is a background job rendering inside a TUI, not a shell.
func PRBuildOptions(ctx context.Context, number int, tag string, log func(string)) (docker.BuildOptions, Cleanup, error) {
if number <= 0 {
return docker.BuildOptions{}, noCleanup, fmt.Errorf("PR number must be positive")
}
if log == nil {
log = func(string) {}
}
temporaryRoot, err := os.MkdirTemp("", "homarr-pr-*")
if err != nil {
return docker.BuildOptions{}, noCleanup, fmt.Errorf("create temporary checkout: %w", err)
}
cleanup := func() { _ = os.RemoveAll(temporaryRoot) }
checkout := filepath.Join(temporaryRoot, "homarr")
if err := os.MkdirAll(checkout, 0o755); err != nil {
cleanup()
return docker.BuildOptions{}, noCleanup, fmt.Errorf("create checkout directory: %w", err)
}
repoURL := fmt.Sprintf("https://github.com/%s.git", gh.Repo)
branchName := fmt.Sprintf("pr-%d", number)
refSpec := fmt.Sprintf("refs/pull/%d/head:%s", number, branchName)
log(fmt.Sprintf("fetching PR #%d (shallow) from %s", number, gh.Repo))
initCmd := exec.CommandContext(ctx, "git", "init", checkout)
if err := runLogged(initCmd, log); err != nil {
cleanup()
return docker.BuildOptions{}, noCleanup, fmt.Errorf("init git repository: %w", err)
}
fetchCmd := exec.CommandContext(ctx, "git", "-C", checkout, "fetch", "--depth", "1", repoURL, refSpec)
if err := runLogged(fetchCmd, log); err != nil {
cleanup()
return docker.BuildOptions{}, noCleanup, fmt.Errorf("fetch PR #%d: %w", number, err)
}
checkoutCmd := exec.CommandContext(ctx, "git", "-C", checkout, "checkout", branchName)
if err := runLogged(checkoutCmd, log); err != nil {
cleanup()
return docker.BuildOptions{}, noCleanup, fmt.Errorf("checkout PR #%d: %w", number, err)
}
_, revision, err := checkoutDetails(ctx, checkout)
if err != nil {
cleanup()
return docker.BuildOptions{}, noCleanup, err
}
log("building " + revision[:min(len(revision), 12)])
return docker.BuildOptions{
Context: checkout,
Tag: tag,
Source: fmt.Sprintf("https://github.com/%s/pull/%d", gh.Repo, number),
Revision: revision,
PRNumber: number,
}, cleanup, nil
}
// RebuildOptions describes a rebuild of an existing local image from the
// provenance recorded on it, whether that is a pull request or a checkout.
func RebuildOptions(ctx context.Context, image docker.Image, log func(string)) (docker.BuildOptions, Cleanup, error) {
if image.PRNumber > 0 {
return PRBuildOptions(ctx, image.PRNumber, image.Tag, log)
}
if image.Source == "" {
return docker.BuildOptions{}, noCleanup, fmt.Errorf("homarr:%s has no build source metadata", image.Tag)
}
if info, err := os.Stat(image.Source); err != nil || !info.IsDir() {
return docker.BuildOptions{}, noCleanup, fmt.Errorf("build source %s is no longer available", image.Source)
}
options, err := CurrentBuildOptions(ctx, image.Tag, image.Source)
return options, noCleanup, err
}
func runLogged(command *exec.Cmd, log func(string)) error {
command.Env = append(
os.Environ(),
"MISE_TRUSTED_CONFIG_PATHS=*",
"MISE_QUIET=1",
"MISE_SILENT=1",
"MISE_YES=1",
"MISE_OVERRIDE_CONFIG_FILENAMES=",
)
lines, err := docker.StreamCommand(command)
if err != nil {
return err
}
for line := range lines {
if line.Done {
return line.Err
}
log(line.Text)
}
return nil
}