package main import ( "bufio" "bytes" "fmt" "hash/crc32" "log" "math/rand" "os" "os/exec" "path/filepath" "regexp" "runtime" "strconv" "strings" "time" "github.com/PumpkinSeed/cage" "github.com/gohugoio/hugo/commands" "github.com/spf13/viper" ) func check(e error) { if e != nil { log.Fatal(e) panic(e) } } func isPublished(gitRepoPath, gitIndexPath, prevcommit, lastcommit string) bool { var sout bytes.Buffer //var serr bytes.Buffer commits := exec.Command("git", "-C", gitRepoPath, "rev-list", fmt.Sprintf("%s..%s", prevcommit, lastcommit)) commits.Env = os.Environ() commits.Env = append(commits.Env, fmt.Sprintf("GIT_INDEX_FILE=%s%s", gitRepoPath, gitIndexPath)) commits.Stdout = &sout //commits.Stderr = &serr err := commits.Run() check(err) for _, commit := range strings.Fields(sout.String()) { committedFiles, err := exec.Command("git", "-C", gitRepoPath, "diff-tree", "--no-commit-id", "--name-only", commit).Output() check(err) for _, commitFile := range strings.Fields(string(committedFiles)) { if commitFile == "PUBLISH.trigger.md" { return true } } commitMessages, err := exec.Command("git", "-C", gitRepoPath, "show", "-s", "--format=%B", commit).Output() check(err) if strings.Contains(string(commitMessages), "!publish!") { return true } } return false } func hugoLogs(logLines string, lines []string, hugoInstance string) string { logLines = logLines + fmt.Sprintf("~~~~~~~~~~ Hugo's logs %s ~~~~~~~~~~\n", hugoInstance) for _, d := range lines { logLines = logLines + d + "\n" } logLines = logLines + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" return logLines } func getOutdirAndConfigCatalog(tmpRepoPath, gitRepoPath string) (string, string, string) { viper.SetConfigName("config") viper.SetConfigType("toml") viper.AddConfigPath(filepath.Join(tmpRepoPath)) err := viper.ReadInConfig() check(err) //outdirBasePath := viper.GetString("params.sandpointsOutputDirectory") outdirChecksum := strconv.FormatUint(uint64(crc32.ChecksumIEEE([]byte(gitRepoPath))), 16) reg, err := regexp.Compile("[^a-zA-Z0-9]+") check(err) outdirRepoName := reg.ReplaceAllString(viper.GetString("title"), "") return (strings.ToLower(outdirRepoName) + "-" + outdirChecksum), viper.GetString("params.sandpointsCatalogName"), viper.GetString("params.sandpointsCatalogPrefix") } func main() { defer os.Exit(0) // outDir is where the output of hugo will end up // make sure the last part of the path has the right permissions // os.TempDir() as first argument in filepath.Join is probably good choice for testing outDir := filepath.Join("/", "var", "www", "html", "sandpoints") startTime := time.Now() logLines := startTime.Format(time.RFC822) + "\n" logGiteaLines := "" ex, err := os.Executable() check(err) var gitRepoPath, prevCommit, lastCommit, branchName, gitIndexPath string scanner := bufio.NewScanner(os.Stdin) scanner.Scan() if scanner.Text() == "" { // if it has no output it comes as post-commit hook gitRepoPath = strings.Replace(filepath.Dir(ex), filepath.Join("/", ".git", "hooks"), "", 1) //gitRepoPath = "/home/m/gitea-repositories/sandpoints/dev.git" gitLastCommit, err := exec.Command("git", "-C", gitRepoPath, "rev-parse", "HEAD").Output() check(err) gitPrevCommit, err := exec.Command("git", "-C", gitRepoPath, "rev-parse", "HEAD^").Output() check(err) gitBranchName, err := exec.Command("git", "-C", gitRepoPath, "branch", "-a", "--contains", "HEAD").Output() check(err) branchName = strings.Replace(strings.TrimSpace(string(gitBranchName)), "* ", "", 1) prevCommit, lastCommit, gitIndexPath = strings.TrimSpace(string(gitPrevCommit)), strings.TrimSpace(string(gitLastCommit)), filepath.Join("/", ".git", "index") } else { // if it has output it comes as post-receive.d/sphook from gitea repo revs := strings.Fields(scanner.Text()) gitRepoPath = strings.Replace(filepath.Dir(ex), filepath.Join("/", "hooks", "post-receive.d"), "", 1) prevCommit, lastCommit, branchName, gitIndexPath = revs[0], revs[1], strings.Split(revs[2], "/")[2], filepath.Join("/", "index") } // there's third one here, revs[2], usually saying refs/heads/master // branchName := fmt.Sprintf(strings.Split(revs[2])[2]) logLines = logLines + "\n" + fmt.Sprintf("Repo: %s\n", gitRepoPath) logLines = logLines + fmt.Sprintf("Repo's branch: %s\n", branchName) rand.Seed(time.Now().UnixNano()) worktreeName := fmt.Sprintf("sandpoints_repo_%06d", rand.Intn(100001)) tmpRepoPath := filepath.Join(os.TempDir(), worktreeName) logLines = logLines + fmt.Sprintf("cloned into temporary directory: %s\n", tmpRepoPath) if _, err := os.Stat(tmpRepoPath); err == nil { err := os.RemoveAll(tmpRepoPath) check(err) } err = os.MkdirAll(tmpRepoPath, 0755) check(err) //var sout bytes.Buffer //var serr bytes.Buffer gitAddWorktree := exec.Command("git", "-C", gitRepoPath, "worktree", "add", "--detach", tmpRepoPath) gitRemoveWorktree := exec.Command("git", "-C", gitRepoPath, "worktree", "remove", worktreeName) gitAddWorktree.Env = os.Environ() gitAddWorktree.Env = append(gitAddWorktree.Env, fmt.Sprintf("GIT_INDEX_FILE=%s%s", gitRepoPath, gitIndexPath)) //gitAddWorktree.Stdout = &sout //gitAddWorktree.Stderr = &serr err = gitAddWorktree.Run() check(err) gitRemoveWorktree.Env = os.Environ() gitRemoveWorktree.Env = append(gitRemoveWorktree.Env, fmt.Sprintf("GIT_INDEX_FILE=%s%s", gitRepoPath, gitIndexPath)) defer gitRemoveWorktree.Run() outSandpointsDir, sandpointsCatalogName, sandpointsCatalogPrefix := getOutdirAndConfigCatalog(tmpRepoPath, gitRepoPath) logLines = logLines + fmt.Sprintf("to be moved to: %s\n", filepath.Join(outDir, outSandpointsDir)) tmpHugoGiteaPath := filepath.Join(os.TempDir(), fmt.Sprintf("sandpoints_hugo_%06d", rand.Intn(100001))) tmpHugoPreviewPath := filepath.Join(tmpHugoGiteaPath, "_preview") err = os.RemoveAll(tmpHugoGiteaPath) check(err) err = os.MkdirAll(tmpHugoPreviewPath, 0755) check(err) err = os.MkdirAll(filepath.Join(outDir, outSandpointsDir, "_preview"), 0755) check(err) published := isPublished(gitRepoPath, gitIndexPath, prevCommit, lastCommit) defer func() { lastPreviewCommitLog, err := os.Create(filepath.Join(outDir, outSandpointsDir, "_preview", "last-commit-log.txt")) check(err) defer lastPreviewCommitLog.Close() fmt.Fprintln(lastPreviewCommitLog, logLines) log.Printf(logLines) }() defer func() { if published { lastGiteaCommitLog, err := os.Create(filepath.Join(outDir, outSandpointsDir, "last-commit-log.txt")) check(err) defer lastGiteaCommitLog.Close() fmt.Fprintln(lastGiteaCommitLog, logGiteaLines) } }() if published { hugoGiteaLogs := cage.Start() respGitea := commands.Execute([]string{"-s", tmpRepoPath, "-d", tmpHugoGiteaPath, "-e", "gitea"}) cage.Stop(hugoGiteaLogs) if respGitea.Err != nil { logGiteaLines = fmt.Sprintf("Attempt to publish web site ended up with an Error! Check out the last commit to capture possible errors.\n\n%s\n~~~~~~~~~~\n\n%s", respGitea.Err, logLines) logLines = fmt.Sprintf("Rendering _preview of the web site ended up with an ERROR! Check out the last commit to capture possible errors.\n\n%s\n~~~~~~~~~~\n\n%s", respGitea.Err, logLines) runtime.Goexit() } logGiteaLines = hugoLogs(logLines, hugoGiteaLogs.Data, "for published instance") } hugoPreviewLogs := cage.Start() respPreview := commands.Execute([]string{"-s", tmpRepoPath, "-d", tmpHugoPreviewPath, "-e", "preview", "--templateMetrics"}) cage.Stop(hugoPreviewLogs) if respPreview.Err != nil { logLines = fmt.Sprintf("Rendering _preview of the web site ended up with an ERROR! Check out the last commit to capture possible errors.\n\n%s\n~~~~~~~~~~\n\n%s", respPreview.Err, logLines) runtime.Goexit() } logLines = hugoLogs(logLines, hugoPreviewLogs.Data, "for _preview instance") if published { if _, err := os.Stat(filepath.Join(outDir, outSandpointsDir)); err == nil { err := os.RemoveAll(filepath.Join(outDir, outSandpointsDir)) check(err) } } else if _, err := os.Stat(filepath.Join(outDir, outSandpointsDir, "_preview")); err == nil { err := os.RemoveAll(filepath.Join(outDir, outSandpointsDir, "_preview")) check(err) } err = os.MkdirAll(outDir, 0755) check(err) if published { err = os.Rename(tmpHugoGiteaPath, filepath.Join(outDir, outSandpointsDir)) check(err) if sandpointsCatalogPrefix != "" && sandpointsCatalogName != "" { err = os.Symlink(filepath.Join(outDir, "libraries", sandpointsCatalogName), filepath.Join(outDir, outSandpointsDir, sandpointsCatalogPrefix)) check(err) err = os.Symlink(filepath.Join(outDir, "libraries", sandpointsCatalogName), filepath.Join(outDir, outSandpointsDir, "_preview", sandpointsCatalogPrefix)) check(err) } } else { err = os.Rename(tmpHugoPreviewPath, filepath.Join(outDir, outSandpointsDir, "_preview")) check(err) if sandpointsCatalogPrefix != "" && sandpointsCatalogName != "" { err = os.Symlink(filepath.Join(outDir, "libraries", sandpointsCatalogName), filepath.Join(outDir, outSandpointsDir, "_preview", sandpointsCatalogPrefix)) check(err) } } publishedMessage := "" if published { publishedMessage = " + published instance" } durationMillseconds := int64(time.Since(startTime) / time.Millisecond) logLines = logLines + fmt.Sprintf("Total processing time (_preview%s): %d ms", publishedMessage, durationMillseconds) logGiteaLines = logGiteaLines + fmt.Sprintf("Total processing time (published + _preview instance): %d ms", durationMillseconds) }