package repostatus import ( "errors" "fmt" "os/user" "strings" "time" "unicode/utf8" "io/ioutil" "go.wit.com/lib/gui/shell" "go.wit.com/log" ) func (rs *RepoStatus) GetCurrentBranchName() string { return rs.currentBranch.String() } func (rs *RepoStatus) GetCurrentBranchVersion() string { return rs.currentVersion.String() } func (rs *RepoStatus) LastGitPull() (time.Time, error) { return rs.mtime(".git/FETCH_HEAD") } func (rs *RepoStatus) Age() time.Duration { var t *Tag t = rs.NewestTag() if t != nil { log.Log(REPO, "newest tag:", t.date.String(), t.tag.String(), t.Name()) return t.Age() } const gitLayout = "Mon Jan 2 15:04:05 2006 -0700" const madeuptime = "Mon Jun 3 15:04:05 2013 -0700" tagTime, _ := time.Parse(gitLayout, madeuptime) return time.Since(tagTime) } var ErrorMissingGitConfig error = errors.New("missing .git/config") var ErrorGitPullOnLocal error = errors.New("git pull on local only branch") func (rs *RepoStatus) GitPull() (string, error) { currentName := rs.GetCurrentBranchName() if rs.IsOnlyLocalTag(currentName) { return "", ErrorGitPullOnLocal } var cmd []string cmd = append(cmd, "git", "pull") r := rs.Run(cmd) output := strings.Join(r.Stdout, "\n") if r.Error != nil { output = "git error_,,,_a_,,,_b_,,,c" } if r.Error == nil { log.Log(REPOWARN, "git pull ran", rs.Path()) log.Log(REPOWARN, "git pull output", output) } else { log.Log(REPOWARN, "git pull error", rs.Path(), r.Error) } return output, r.Error } /* // this isn't right func (rs *RepoStatus) LastTagAge() (time.Time, string) { return time.Now(), rs.lasttag.String() } */ func (rs *RepoStatus) GetLastTagVersion() string { return rs.lasttag.String() } // stores the current branch name func (rs *RepoStatus) checkCurrentBranchName() string { currentname := rs.currentBranch.String() out := run(rs.realPath.String(), "git", "branch --show-current") if currentname == out { // nothing changed return currentname } rs.currentBranch.SetValue(out) if currentname == "" { return out // don't note if there was nothing before } rs.NoteChange("current branch has changed from " + currentname + " to " + out) return out } func (rs *RepoStatus) gitDescribeByHash(hash string) (string, error) { if hash == "" { return "", errors.New("hash was blank") } r := shell.PathRunLog(rs.Path(), []string{"git", "describe", "--tags", "--always", hash}, INFO) out := strings.Join(r.Stdout, "\n") if r.Error != nil { log.Warn("not in a git repo or bad hash?", r.Error, rs.Path()) return out, r.Error } return out, r.Error } func (rs *RepoStatus) gitDescribeByName(name string) (string, error) { name = strings.TrimSpace(name) if name == "" { // git will return the current tag r := shell.PathRunLog(rs.Path(), []string{"git", "describe", "--tags", "--always"}, INFO) output := strings.Join(r.Stdout, "\n") if r.Error != nil { log.Warn("gitDescribeByName() not in a git repo?", r.Error, rs.Path()) } return strings.TrimSpace(output), r.Error } if !rs.LocalTagExists(name) { // tag does not exist return "", errors.New("gitDescribeByName() git fatal: Not a valid object name") } cmd := []string{"git", "describe", "--tags", "--always", name} r := shell.PathRunLog(rs.Path(), cmd, INFO) output := strings.Join(r.Stdout, "\n") if r.Error != nil { log.Warn("cmd =", cmd) log.Warn("err =", r.Error) log.Warn("not in a git repo or bad tag?", rs.Path()) } return strings.TrimSpace(output), r.Error } // todo: don't run git every time? func (rs *RepoStatus) checkCurrentBranchVersion() string { out, _ := rs.gitDescribeByName("") log.Log(REPO, "checkCurrentBranchVersion()", out) rs.currentVersion.SetValue(out) return out } // this should get the most recent tag func (rs *RepoStatus) setLastTagVersion() { hash := run(rs.realPath.String(), "git", "rev-list --tags --max-count=1") log.Log(REPO, "getLastTagVersion()", hash) name, _ := rs.gitDescribeByHash(hash) rs.lasttag.SetText(name) return } func (rs *RepoStatus) populateTags() { tmp := rs.realPath.String() + "/.git/refs/tags" log.Log(REPO, "populateTags() path =", tmp) for _, tag := range listFiles(tmp) { if rs.tags[tag] == "" { log.Log(REPO, "populateTags() Adding new tag", tag) // rs.tagsDrop.AddText(tag) rs.tags[tag] = "origin" } } // rs.tagsDrop.SetText(rs.lasttagrev) } func (rs *RepoStatus) getBranches() []string { var all []string var heads []string var remotes []string heads = listFiles(rs.realPath.String() + "/.git/refs/heads") remotes = listFiles(rs.realPath.String() + "/.git/refs/remotes") all = heads all = append(all, remotes...) for _, branch := range all { log.Log(REPO, "getBranches()", branch) } return all } // returns quickly based on the last time it was checked func (rs *RepoStatus) IsDirty() bool { if rs.dirtyLabel.String() == "no" { return false } return true } // return the list of dirty files (but ignores go.mod & go.sum) func (rs *RepoStatus) DirtyList() []string { var all []string for _, line := range strings.Split(rs.dirtyList, "\n") { line = strings.TrimSpace(line) parts := strings.Split(line, " ") if len(parts) != 2 { continue } if parts[1] == "go.mod" { continue } if parts[1] == "go.sum" { continue } all = append(all, parts[1]) } return all } func (rs *RepoStatus) CheckDirty() bool { var start string = rs.dirtyLabel.String() cmd := []string{"git", "status", "--porcelain"} r := shell.PathRunLog(rs.Path(), cmd, INFO) out := strings.Join(r.Stdout, "\n") if r.Error != nil { log.Warn("CheckDirty() status cmd =", cmd) log.Warn("CheckDirty() status out =", out) log.Warn("CheckDirty() status err =", r.Error) log.Error(r.Error, "CheckDirty() git status error") rs.dirtyLabel.SetValue("error") if start != "error" { rs.NoteChange("git status is in error " + fmt.Sprint(r.Error)) } return true } rs.dirtyList = out // last := out[strings.LastIndex(out, "\n")+1:] // if last == "nothing to commit, working tree clean" { if len(rs.DirtyList()) == 0 { log.Log(REPO, "CheckDirty() no", rs.realPath.String()) rs.dirtyLabel.SetValue("no") if start == "" { // don't record a change as this is the initial run return false } if start != "no" { log.Log(REPOWARN, "is no longer dirty") rs.NoteChange("is no longer dirty") } return false } rs.dirtyLabel.SetValue("dirty") if start == "" { // don't record a change as this is the initial run return false } if start != "dirty" { log.Log(REPOWARN, "is now dirty") rs.NoteChange("is now dirty") } return true } func (rs *RepoStatus) CheckoutBranch(bname string) bool { if rs.CheckDirty() { log.Log(REPO, rs.realPath.String(), "is dirty") log.Info(bname, "is dirty", rs.Path()) return false } if !rs.TagExists(bname) { // tag does not exist log.Log(REPO, "repo does not have branch", bname, rs.Path()) return false } cName := rs.GetCurrentBranchName() if cName == bname { // already on branch return true } cmd := []string{"git", "checkout", bname} r := rs.Run(cmd) if r.Error != nil { log.Log(REPO, "git checkout error:", r.Error) return false } rs.checkCurrentBranchName() rs.checkCurrentBranchVersion() return true } func (rs *RepoStatus) CheckoutMaster() bool { if rs.CheckDirty() { log.Log(REPO, rs.realPath.String(), "is dirty") return false } mName := rs.GetMasterBranchName() if rs.CheckoutBranch(mName) { return true } return true } func (rs *RepoStatus) CheckoutDevel() bool { devel := rs.develWorkingName.String() // user := rs.userWorkingName.String() if devel == "" { return false } if rs.CheckDirty() { log.Log(REPO, rs.realPath.String(), "is dirty") return false } log.Log(REPO, "checkoutBranch", devel) rs.checkoutBranch("devel", devel) // log.Log(REPO, "checkoutBranch", user) // rs.checkoutBranch("user", user) return true } func (rs *RepoStatus) CheckoutUser() bool { bName := rs.GetUserBranchName() if bName == "" { return false } if rs.CheckDirty() { log.Log(REPO, rs.realPath.String(), "is dirty") return false } if !rs.BranchExists(bName) { return false } cmd := []string{"git", "checkout", bName} r := rs.Run(cmd) if r.Error != nil { log.Log(REPO, "git checkout error:", r.Error) } realname := rs.GetCurrentBranchName() realversion := rs.GetCurrentBranchVersion() log.Log(REPO, rs.realPath.String(), "realname =", realname, "realversion =", realversion) if realname != bName { log.Log(REPO, "git checkout failed", rs.realPath.String(), bName, "!=", realname) return false } rs.userBranchVersion.SetValue(realversion) return true } func (rs *RepoStatus) checkoutBranch(level string, branch string) { if rs.CheckDirty() { log.Log(REPO, "checkoutBranch() checkDirty() == true for repo", rs.realPath.String(), "looking for branch:", branch) return } out := run(rs.realPath.String(), "git", "checkout "+branch) log.Log(REPO, rs.realPath.String(), "git checkout "+branch, "returned", out) realname := rs.GetCurrentBranchName() realversion := rs.GetCurrentBranchVersion() log.Log(REPO, rs.realPath.String(), "realname =", realname, "realversion =", realversion) switch level { case "master": rs.mainBranchVersion.SetValue(realversion) case "devel": rs.develBranchVersion.SetValue(realversion) case "user": rs.userBranchVersion.SetValue(realversion) default: } } // attempt's to guess at what master is. // TODO: fix this properly func (rs *RepoStatus) guessMainWorkingName() { if !rs.Ready() { return } if rs.TagExists("guimaster") { rs.mainWorkingName.SetText("guimaster") rs.mainBranchVersion.SetLabel("guimaster") return } if rs.TagExists("master") { rs.mainWorkingName.SetText("master") rs.mainBranchVersion.SetLabel("master") return } if rs.TagExists("main") { rs.mainWorkingName.SetText("main") rs.mainBranchVersion.SetLabel("main") return } // figure out what to do here rs.mainWorkingName.SetText("FIXME") rs.mainBranchVersion.SetLabel("FIXME") } func (rs *RepoStatus) guessDevelWorkingName() { if rs.TagExists("guidevel") { rs.develWorkingName.SetValue("guidevel") rs.develBranchVersion.SetLabel("guidevel") return } if rs.TagExists("devel") { rs.develWorkingName.SetValue("devel") rs.develBranchVersion.SetLabel("devel") return } // figure out what to do here rs.develWorkingName.SetValue("develFIXME") rs.develBranchVersion.SetLabel("develFIXME") } func (rs *RepoStatus) setUserWorkingName() { usr, _ := user.Current() uname := usr.Username if rs.TagExists(uname) { rs.userWorkingName.SetValue(uname) rs.userBranchVersion.SetLabel(uname) return } rs.userWorkingName.SetValue("need to create " + uname) rs.userBranchVersion.SetLabel("need to create " + uname) } // returns "master", "devel", os.Username, etc func (rs *RepoStatus) GetMasterBranchName() string { name := rs.mainWorkingName.String() return name } func (rs *RepoStatus) GetDevelBranchName() string { name := rs.develWorkingName.String() return name } func (rs *RepoStatus) GetUserBranchName() string { name := rs.userWorkingName.String() return name } // returns the git versions like "1.3-2-laksdjf" or whatever func (rs *RepoStatus) GetMasterVersion() string { name := rs.mainBranchVersion.String() return name } func (rs *RepoStatus) GetDevelVersion() string { name := rs.develBranchVersion.String() return name } func (rs *RepoStatus) GetUserVersion() string { name := rs.userBranchVersion.String() return name } func (rs *RepoStatus) setMasterVersion(s string) { old := rs.GetMasterVersion() if old == s { return } rs.mainBranchVersion.SetValue(s) if old == "" { return // don't note if there was nothing before } rs.NoteChange("master branch has been changed from " + old + " to " + s) } func (rs *RepoStatus) setDevelVersion(s string) { old := rs.GetDevelVersion() if old == s { return } if old == "" { // don't note nothing } else { rs.NoteChange("devel branch has been changed from " + old + " to " + s) } rs.develBranchVersion.SetValue(s) } func (rs *RepoStatus) setUserVersion(s string) { old := rs.GetUserVersion() if old == s { return } if old == "" { // don't note nothing } else { rs.NoteChange("user branch has been changed from " + old + " to " + s) } rs.userBranchVersion.SetValue(s) } func (rs *RepoStatus) GitState() string { return rs.gitState.String() } func (rs *RepoStatus) CheckGitState() string { rs.setState() return rs.gitState.String() } func (rs *RepoStatus) GetStatus() string { return rs.gitState.String() } func (rs *RepoStatus) setState() { rs.changed = false if rs.CheckDirty() { log.Log(REPO, "CheckDirty() true") rs.gitState.SetText("dirty") return } if rs.GetUserVersion() != rs.GetDevelVersion() { rs.gitState.SetText("merge to devel") return } if rs.GetDevelVersion() != rs.GetMasterVersion() { rs.gitState.SetText("merge to main") return } if rs.lasttag.String() != rs.GetMasterVersion() { rs.gitState.SetText("unchanged") return } if rs.CheckBranches() { log.Log(REPO, "Branches are Perfect") rs.gitState.SetText("PERFECT") return } log.Log(REPO, rs.String(), "Branches are not Perfect") rs.gitState.SetText("unknown branches") } // TODO: make this report the error somewhere // This is supposed to check all the branches to make sure // the are the same. that was originally what this was for // now I think it's jsut probably dumb old code that doesn't // need to be here // actually, this is to attempt to verify absolutely everything // is pushed upstream before doing a rm -rf ~/go/src // TODO: revisit this code in the autotypist later func (rs *RepoStatus) CheckBranches() bool { var hashCheck string var perfect bool = true all := rs.getBranches() path := rs.realPath.String() + "/.git/refs/" for _, b := range all { parts := strings.Split(b, "/") rdir := "heads" if len(parts) == 2 { rdir = "remotes" } fullfile := path + "/" + rdir + "/" + b // check if the ref name is "HEAD". if so, skip runeCount := utf8.RuneCountInString(fullfile) // Convert the string to a slice of runes runes := []rune(fullfile) // Slice the last 4 runes lastFour := runes[runeCount-4:] if string(lastFour) == "HEAD" { log.Log(REPO, "skip HEAD fullfile", fullfile) continue } content, _ := ioutil.ReadFile(fullfile) hash := strings.TrimSpace(string(content)) if hashCheck == "" { hashCheck = hash } var cmd []string cmd = append(cmd, "git", "show", "-s", "--format=%ci", hash) r := shell.PathRunLog(rs.Path(), cmd, INFO) if r.Error != nil { log.Log(WARN, "CheckBranches() git show error:", r.Error) } // git show -s --format=%ci will give you the time // log.Log(REPO, fullfile) if hash == hashCheck { log.Log(REPO, "notsure why this git show is here", hash) } else { // log.Log(WARN, rs.String(), hash, output, b) // log.Log(WARN, "UNKNOWN BRANCHES IN THIS REPO", cmd) rs.versionCmdOutput.SetText("UNKNOWN BRANCHES") perfect = false // parts := strings.Split(b, "/") // log.Warn("git push", parts) } } return perfect }