package gitpb // functions to import and export the protobuf // data to and from config files import ( "errors" "os" "path/filepath" "go.wit.com/lib/protobuf/bugpb" "go.wit.com/log" ) // write to ~/.config/forge/ unless ENV{FORGE_GOSRC} is set func (all *Repos) ConfigSave() error { if os.Getenv("FORGE_GOSRC") == "" { homeDir, _ := os.UserHomeDir() fullpath := filepath.Join(homeDir, ".config/forge") os.Setenv("FORGE_GOSRC", fullpath) } if all == nil { log.Warn("gitpb all == nil") return errors.New("gitpb.ConfigSave() all == nil") } data, err := all.Marshal() if err != nil { log.Info("gitpb proto.Marshal() failed len", len(data), err) // often this is because strings have invalid UTF-8. This should probably be fixed in the protobuf code if err := all.tryValidate(); err != nil { return err } else { // re-attempt Marshal() here data, err = all.Marshal() if err == nil { // validate & sanitize strings worked log.Info("gitpb.ConfigSave() repos.Marshal() worked len", len(all.Repos), "repos") configWrite("repos.pb", data) return nil } } return err } log.Info("gitpb.ConfigSave() repos.Marshal() worked len", len(all.Repos), "repos") configWrite("repos.pb", data) return nil } func (all *Repos) tryValidate() error { err := bugpb.ValidateProtoUTF8(all) if err != nil { log.Printf("Protobuf UTF-8 validation failed: %v\n", err) } if err := bugpb.SanitizeProtoUTF8(all); err != nil { log.Warn("Sanitation failed:", err) // log.Fatalf("Sanitization failed: %v", err) return err } return nil } // load the repos.pb file. I shouldn't really matter if this // fails. the file should be autogenerated. This is used // locally just for speed func (all *Repos) ConfigLoad() error { if os.Getenv("FORGE_GOSRC") == "" { homeDir, _ := os.UserHomeDir() fullpath := filepath.Join(homeDir, ".config/forge") os.Setenv("FORGE_GOSRC", fullpath) } var data []byte var err error cfgname := filepath.Join(os.Getenv("FORGE_GOSRC"), "repos.pb") if data, err = loadFile(cfgname); err != nil { // something went wrong loading the file // all.sampleConfig() // causes nil panic return err } // this means the forge.pb file exists and was read if len(data) == 0 { all.sampleConfig() // causes nil panic return errors.New("gitpb.ConfigLoad() repos.pb is empty") } err = all.Unmarshal(data) test := NewRepos() if test.Uuid != all.Uuid { log.Log(WARN, "uuids do not match", test.Uuid, all.Uuid) deleteProtobufFile(cfgname) } if test.Version != all.Version { log.Log(WARN, "versions do not match", test.Version, all.Version) deleteProtobufFile(cfgname) } log.Log(INFO, cfgname, "protobuf versions and uuid match", all.Uuid, all.Version) return err } func deleteProtobufFile(filename string) { log.Log(WARN, "The protobuf file format has changed for", filename) log.Log(WARN, "You must delete", filename) log.Log(WARN, "This file will be recreated") os.Exit(-1) } func (all *Repos) sampleConfig() { newr := new(Repo) newr.FullPath = "/opt/forge/dummyentry" all.Append(newr) } func loadFile(fullname string) ([]byte, error) { data, err := os.ReadFile(fullname) if errors.Is(err, os.ErrNotExist) { // if file does not exist, just return nil. this // will cause ConfigLoad() to try the next config file like "forge.text" // because the user might want to edit the .config by hand return nil, nil } if err != nil { // log.Info("open config file :", err) return nil, err } return data, nil } func configWrite(filename string, data []byte) error { fullname := filepath.Join(os.Getenv("FORGE_GOSRC"), filename) cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) defer cfgfile.Close() if err != nil { log.Warn("open config file :", err) return err } if filename == "forge.text" { // add header cfgfile.Write([]byte("# this file is automatically re-generated from forge.pb, however,\n")) cfgfile.Write([]byte("# if you want to edit it by hand, you can:\n")) cfgfile.Write([]byte("# stop forge; remove forge.pb; edit forge.text; start forge\n")) cfgfile.Write([]byte("# this will cause the default behavior to fallback to parsing this file for the config\n")) cfgfile.Write([]byte("\n")) cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n")) cfgfile.Write([]byte("# git repos you have write access to. That is, where you can run 'git push'\n")) } cfgfile.Write(data) return nil }