// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 package main import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/go-cmd/cmd" "go.wit.com/lib/config" "go.wit.com/lib/env" "go.wit.com/lib/fhelp" "go.wit.com/lib/gui/shell" "go.wit.com/lib/protobuf/argvpb" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" ) func doGitCreate(namespace string) (string, error) { var s string var err error templateDir := "/var/lib/git/lib/templates/golib" if !shell.IsDir(templateDir) { return "", errors.New("no template/golib") } fullpath := filepath.Join("/var/lib/git", namespace) if shell.IsDir(fullpath) { return "", errors.New("dir " + fullpath + " already exists") } basepath, _ := filepath.Split(fullpath) if err := os.MkdirAll(basepath, os.ModePerm); err != nil { return "", err } cmd := []string{"cp", "-a", templateDir, fullpath} shell.RunVerbose(cmd) return s, err } func runCommand(cmds []string) (string, error) { var r cmd.Status // var err error s := log.Sprintf("cmd= %v ", cmds) log.Info("GOING TO RUN?", s) if fhelp.QuestionUser("Run " + s + " here") { r = shell.Run(cmds) } return "Ran " + s, r.Error } func doGit() (string, error) { var s string var err error var track []string if env.Get("track") == "" { env.Set("track", "~/.config/wit") env.Save() log.Info("Setting tracking on", env.Get("track")) } for _, p := range strings.Fields(env.Get("track")) { fullp := env.FullPath(p) if config.IsDir(fullp) { log.Info("tracking repo:", fullp) track = append(track, fullp) } else { log.Info("missing repo:", fullp) } } if argv.Git.Log != nil { fstr := "--format=\"%h %>(24)%ar %>(20)%an %s" cmd := []string{"git", "log", fstr} shell.RunVerbose(cmd) s = "git log" } if argv.Git.Create != "" { s = "attmepting to create new repo" } if argv.Git.Template != "" { } if argv.Git.Edit != "" { log.Info("change last git commit message to:", argv.Git.Edit) cmd := []string{"git", "commit", "--amend", "-m", argv.Git.Edit} s, err = runCommand(cmd) } if argv.Git.ChopHEAD != 0 { cmd := []string{"git", "reset", "--hard", fmt.Sprintf("HEAD-%d", argv.Git.ChopHEAD)} s, err = runCommand(cmd) } if argv.Git.Tag != nil { cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format"} cmd = append(cmd, "%(tag)%00%(taggerdate:raw)%00%(taggername)%00%(subject)") cmd = append(cmd, "refs/tags") result := shell.RunQuiet(cmd) for i, line := range result.Stdout { parts := strings.Split(line, "\x00") log.Infof("LINE: %d len(%d) %v\n", i, len(parts), parts) } s = "git tags" } if argv.Git.Who != nil { if _, err := fhelp.CheckCmd("git-who"); err != nil { if fhelp.QuestionUser("install git-who") { log.Info("go install -v -x github.com/sinclairtarget/git-who@latest") } else { log.Info("not installing") } argvpb.GoodExit("git who should be installed") } cmd := []string{"git", "who", "-l", "."} shell.RunVerbose(cmd) s = "git who" } if argv.Git.Pull != nil { for _, fullp := range track { doPull(fullp) } s = "git pull" } if argv.Git.Push != nil { for _, fullp := range track { err := doPush(fullp) if err != nil { return "git push failed", err } } s = "git push worked" } if argv.Git.DeleteUntracked { initForge() var totals, repos int for repo := range me.forge.Repos.IterAll() { if !strings.HasPrefix(repo.Namespace, "go.wit.com") { continue } files, _ := repo.GitDeleteOthers() if len(files) == 0 { continue } totals += len(files) repos += 1 log.Info(len(files), repo.Namespace) log.Info(strings.Join(files, "\n")) if argv.Force { if len(files) > 0 { cmd := []string{"rm"} cmd = append(cmd, files...) _, err := fhelp.RunRealtimeError(cmd) if err != nil { log.Info("cmd failed", cmd, repo.FullPath, err) return "rm failed", err } } } } s = log.Sprintf("total files to delete: (%d) in (%d) repos", totals, repos) } return s, err } func doPull(fpath string) error { repo, err := gitpb.NewRepo(fpath) if err != nil { log.Info("path error", fpath, err) return err } os.Chdir(repo.FullPath) cmd := []string{"git", "pull"} log.Info("Run", repo.FullPath, cmd) err = repo.RunVerbose(cmd) return err } func doPush(fpath string) error { doPull(fpath) repo, err := gitpb.NewRepo(fpath) if err != nil { log.Info("path error", fpath, err) return err } if repo == nil { log.Info("repo is nil", fpath, err) return errors.New("not git repo " + fpath) } if err := repo.GitCommit(); err != nil { // msg := fmt.Sprintf("repo.GitCommit() %s err(%v)", repo.FullPath, err) return err } err = repo.RunVerbose([]string{"git", "push"}) return err } /* ┌─────────────┬───────────────────────────┬──────────────────────────────────────────┐ │ Placeholder │ Description │ Example Output │ ├─────────────┼───────────────────────────┼──────────────────────────────────────────┤ │ %H │ Full commit hash │ a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 │ │ %h │ Abbreviated commit hash │ a1b2c3d │ │ %T │ Full tree hash │ f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4b5a6f1e2 │ │ %t │ Abbreviated tree hash │ f1e2d3c │ │ %P │ Full parent hashes │ a1b2... f1e2... (space-separated) │ │ %p │ Abbreviated parent hashes │ a1b2c3d f1e2d3c │ └─────────────┴───────────────────────────┴──────────────────────────────────────────┘ Author and Committer Information (Note: "Author" is who originally wrote the code. "Committer" is who last applied it to the branch. They are often the same.) ┌─────────────┬───────────────────────────────────┬────────────────────────────────┐ │ Placeholder │ Description │ Example Output │ ├─────────────┼───────────────────────────────────┼────────────────────────────────┤ │ %an │ Author name │ Jane Doe │ │ %ae │ Author email │ jane.doe@example.com │ │ %aN │ Author name (respecting .mailmap) │ Jane Doe │ │ %ad │ Author date │ Tue Oct 14 10:30:00 2025 -0700 │ │ %ar │ Author date, relative │ 3 days ago │ │ %at │ Author date, UNIX timestamp │ 1760453400 │ │ %cn │ Committer name │ John Smith │ │ %ce │ Committer email │ john.smith@example.com │ │ %cd │ Committer date │ Wed Oct 15 11:00:00 2025 -0700 │ │ %cr │ Committer date, relative │ 2 days ago │ │ %ct │ Committer date, UNIX timestamp │ 1760541600 │ └─────────────┴───────────────────────────────────┴────────────────────────────────┘ Commit Message ┌─────────────┬──────────────────────────────────────────────────────────┐ │ Placeholder │ Description │ ├─────────────┼──────────────────────────────────────────────────────────┤ │ %s │ Subject (first line of the commit message) │ │ %f │ "Sanitized" subject line, suitable for a filename │ │ %b │ Body (the rest of the commit message, after the subject) │ │ %B │ Raw full commit message (subject + body) │ │ %N │ Commit notes (from git notes) │ └─────────────┴──────────────────────────────────────────────────────────┘ Ref Names (Branches and Tags) ┌────────────┬─────────────────────────────────────────────────────────────┬──────────────────────────────────────┐ │ Placeho... │ Description │ Example Output │ ├────────────┼─────────────────────────────────────────────────────────────┼──────────────────────────────────────┤ │ %d │ All ref names pointing to this commit │ `(HEAD -> main, tag: v1.0, origin... │ │ %D │ Like %d, but without the parentheses/commas │ HEAD -> main, tag: v1.0, origin/main │ │ %S │ The ref name given on the command line (e.g., the branch... │ main │ └────────────┴─────────────────────────────────────────────────────────────┴──────────────────────────────────────┘ */