207 lines
5.7 KiB
Go
207 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
func updateTemplates(config Config) (string, error) {
|
|
log.Printf("Updating templates from Git: %s (branch: %s)", sanitizeRepoURL(config.TemplatesRepo), config.TemplatesBranch)
|
|
|
|
// Check if templates directory exists
|
|
if _, err := os.Stat(config.TemplatesPath); os.IsNotExist(err) {
|
|
// Clone repository
|
|
if err := cloneRepository(config); err != nil {
|
|
return "", err
|
|
}
|
|
return getCurrentCommit(config.TemplatesPath), nil
|
|
}
|
|
|
|
// Check if it's a valid git repository
|
|
if !isGitRepository(config.TemplatesPath) {
|
|
log.Printf("Warning: %s exists but is not a git repository", config.TemplatesPath)
|
|
log.Printf("Remove the directory or use -skip-git-pull flag")
|
|
return "", fmt.Errorf("not a git repository")
|
|
}
|
|
|
|
// Pull latest changes and detect if there were updates
|
|
if err := pullRepository(config); err != nil {
|
|
return getCurrentCommit(config.TemplatesPath), err
|
|
}
|
|
|
|
return getCurrentCommit(config.TemplatesPath), nil
|
|
}
|
|
|
|
func cloneRepository(config Config) error {
|
|
log.Printf("Cloning templates repository...")
|
|
|
|
repoURL := buildRepoURL(config.TemplatesRepo, config.GitToken)
|
|
|
|
cmd := exec.Command("git", "clone", "-b", config.TemplatesBranch, repoURL, config.TemplatesPath)
|
|
cmd.Stdout = os.Stdout
|
|
// Don't show stderr to avoid leaking token
|
|
var stderr strings.Builder
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
// Sanitize error message
|
|
errMsg := sanitizeGitError(stderr.String())
|
|
return fmt.Errorf("failed to clone repository: %w\n%s", err, errMsg)
|
|
}
|
|
|
|
log.Printf("Templates cloned successfully")
|
|
showCurrentCommit(config.TemplatesPath)
|
|
return nil
|
|
}
|
|
|
|
func pullRepository(config Config) error {
|
|
// Get current commit before pull
|
|
oldCommit := getCurrentCommit(config.TemplatesPath)
|
|
|
|
log.Printf("Pulling latest templates...")
|
|
|
|
// Update remote URL with token if provided
|
|
if config.GitToken != "" {
|
|
if err := updateRemoteURL(config); err != nil {
|
|
log.Printf("Warning: Failed to update remote URL: %v", err)
|
|
}
|
|
}
|
|
|
|
cmd := exec.Command("git", "-C", config.TemplatesPath, "pull", "origin", config.TemplatesBranch)
|
|
var stdout, stderr strings.Builder
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
errMsg := sanitizeGitError(stderr.String())
|
|
return fmt.Errorf("failed to pull repository: %w\n%s", err, errMsg)
|
|
}
|
|
|
|
// Get current commit after pull
|
|
newCommit := getCurrentCommit(config.TemplatesPath)
|
|
|
|
// Check if there were changes
|
|
if oldCommit == newCommit {
|
|
log.Printf("No new changes in templates (commit: %s)", truncateHash(oldCommit))
|
|
} else if oldCommit == "" {
|
|
log.Printf("Successfully pulled templates")
|
|
showCurrentCommit(config.TemplatesPath)
|
|
} else {
|
|
log.Printf("Templates updated: %s -> %s", truncateHash(oldCommit), truncateHash(newCommit))
|
|
showCommitDiff(config.TemplatesPath, oldCommit, newCommit)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func updateRemoteURL(config Config) error {
|
|
repoURL := buildRepoURL(config.TemplatesRepo, config.GitToken)
|
|
cmd := exec.Command("git", "-C", config.TemplatesPath, "remote", "set-url", "origin", repoURL)
|
|
return cmd.Run()
|
|
}
|
|
|
|
func buildRepoURL(repoURL, token string) string {
|
|
if token == "" {
|
|
return repoURL
|
|
}
|
|
|
|
// For Gitea, format: https://TOKEN@svc-git.cirex.ru/...
|
|
// Remove https:// prefix if present
|
|
cleanURL := strings.TrimPrefix(repoURL, "https://")
|
|
cleanURL = strings.TrimPrefix(cleanURL, "http://")
|
|
|
|
return fmt.Sprintf("https://%s@%s", token, cleanURL)
|
|
}
|
|
|
|
func sanitizeRepoURL(repoURL string) string {
|
|
// Remove token from URL for logging
|
|
if strings.Contains(repoURL, "@") {
|
|
parts := strings.SplitN(repoURL, "@", 2)
|
|
if len(parts) == 2 {
|
|
// Keep protocol and domain
|
|
protocol := "https://"
|
|
if strings.HasPrefix(repoURL, "http://") {
|
|
protocol = "http://"
|
|
}
|
|
return protocol + "***:***@" + parts[1]
|
|
}
|
|
}
|
|
return repoURL
|
|
}
|
|
|
|
func sanitizeGitError(errMsg string) string {
|
|
// Remove potential token from error messages
|
|
// Support both Gitea (TOKEN@) and GitLab (oauth2:TOKEN@) formats
|
|
|
|
// Gitea format: https://TOKEN@host
|
|
if strings.Contains(errMsg, "@") && strings.Contains(errMsg, "://") {
|
|
parts := strings.Split(errMsg, "://")
|
|
for i := 1; i < len(parts); i++ {
|
|
atIndex := strings.Index(parts[i], "@")
|
|
if atIndex > 0 {
|
|
parts[i] = "***@" + parts[i][atIndex+1:]
|
|
}
|
|
}
|
|
errMsg = strings.Join(parts, "://")
|
|
}
|
|
|
|
// GitLab format: https://oauth2:TOKEN@host (legacy support)
|
|
if strings.Contains(errMsg, "oauth2:") {
|
|
parts := strings.Split(errMsg, "oauth2:")
|
|
for i := 1; i < len(parts); i++ {
|
|
atIndex := strings.Index(parts[i], "@")
|
|
if atIndex > 0 {
|
|
parts[i] = "***" + parts[i][atIndex:]
|
|
}
|
|
}
|
|
errMsg = strings.Join(parts, "oauth2:")
|
|
}
|
|
|
|
return errMsg
|
|
}
|
|
|
|
func isGitRepository(path string) bool {
|
|
gitDir := path + "/.git"
|
|
_, err := os.Stat(gitDir)
|
|
return err == nil
|
|
}
|
|
|
|
func getCurrentCommit(templatesPath string) string {
|
|
cmd := exec.Command("git", "-C", templatesPath, "rev-parse", "HEAD")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(output))
|
|
}
|
|
|
|
func showCurrentCommit(templatesPath string) {
|
|
cmd := exec.Command("git", "-C", templatesPath, "log", "-1", "--oneline")
|
|
output, err := cmd.Output()
|
|
if err == nil {
|
|
log.Printf("Current template commit: %s", strings.TrimSpace(string(output)))
|
|
}
|
|
}
|
|
|
|
func showCommitDiff(templatesPath, oldCommit, newCommit string) {
|
|
// Show files changed
|
|
cmd := exec.Command("git", "-C", templatesPath, "diff", "--name-only", oldCommit, newCommit)
|
|
output, err := cmd.Output()
|
|
if err == nil && len(output) > 0 {
|
|
files := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
log.Printf("Changed files:")
|
|
for _, file := range files {
|
|
log.Printf(" - %s", file)
|
|
}
|
|
}
|
|
}
|
|
|
|
func truncateHash(hash string) string {
|
|
if len(hash) > 8 {
|
|
return hash[:8]
|
|
}
|
|
return hash
|
|
}
|