github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/env/repo_state.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package env 16 17 import ( 18 "context" 19 "encoding/json" 20 "os" 21 22 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 23 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 24 "github.com/dolthub/dolt/go/libraries/utils/concurrentmap" 25 "github.com/dolthub/dolt/go/libraries/utils/filesys" 26 "github.com/dolthub/dolt/go/store/hash" 27 "github.com/dolthub/dolt/go/store/types" 28 ) 29 30 // TODO: change name to ClientStateReader, move out of env package 31 type RepoStateReader interface { 32 CWBHeadRef() (ref.DoltRef, error) 33 CWBHeadSpec() (*doltdb.CommitSpec, error) 34 GetRemotes() (*concurrentmap.Map[string, Remote], error) 35 GetBackups() (*concurrentmap.Map[string, Remote], error) 36 GetBranches() (*concurrentmap.Map[string, BranchConfig], error) 37 } 38 39 type RepoStateWriter interface { 40 // TODO: kill this 41 SetCWBHeadRef(context.Context, ref.MarshalableRef) error 42 AddRemote(r Remote) error 43 AddBackup(r Remote) error 44 RemoveRemote(ctx context.Context, name string) error 45 RemoveBackup(ctx context.Context, name string) error 46 TempTableFilesDir() (string, error) 47 UpdateBranch(name string, new BranchConfig) error 48 } 49 50 type RepoStateReadWriter interface { 51 RepoStateReader 52 RepoStateWriter 53 } 54 55 // RemoteDbProvider is an interface for getting a database from a remote 56 type RemoteDbProvider interface { 57 GetRemoteDB(ctx context.Context, format *types.NomsBinFormat, r Remote, withCaching bool) (*doltdb.DoltDB, error) 58 } 59 60 type DbData struct { 61 Ddb *doltdb.DoltDB 62 Rsw RepoStateWriter 63 Rsr RepoStateReader 64 } 65 66 type BranchConfig struct { 67 Merge ref.MarshalableRef `json:"head"` 68 Remote string `json:"remote"` 69 } 70 71 type RepoState struct { 72 Head ref.MarshalableRef `json:"head"` 73 Remotes *concurrentmap.Map[string, Remote] `json:"remotes"` 74 Backups *concurrentmap.Map[string, Remote] `json:"backups"` 75 Branches *concurrentmap.Map[string, BranchConfig] `json:"branches"` 76 // |staged|, |working|, and |merge| are legacy fields left over from when Dolt repos stored this info in the repo 77 // state file, not in the DB directly. They're still here so that we can migrate existing repositories forward to the 78 // new storage format, but they should be used only for this purpose and are no longer written. 79 staged string 80 working string 81 merge *mergeState 82 } 83 84 // repoStateLegacy only exists to unmarshall legacy repo state files, since the JSON marshaller can't work with 85 // unexported fields 86 type repoStateLegacy struct { 87 Head ref.MarshalableRef `json:"head"` 88 Remotes *concurrentmap.Map[string, Remote] `json:"remotes"` 89 Backups *concurrentmap.Map[string, Remote] `json:"backups"` 90 Branches *concurrentmap.Map[string, BranchConfig] `json:"branches"` 91 Staged string `json:"staged,omitempty"` 92 Working string `json:"working,omitempty"` 93 Merge *mergeState `json:"merge,omitempty"` 94 } 95 96 // repoStateLegacyFromRepoState creates a new repoStateLegacy from a RepoState file. Only for testing. 97 func repoStateLegacyFromRepoState(rs *RepoState) *repoStateLegacy { 98 return &repoStateLegacy{ 99 Head: rs.Head, 100 Remotes: rs.Remotes, 101 Backups: rs.Backups, 102 Branches: rs.Branches, 103 Staged: rs.staged, 104 Working: rs.working, 105 Merge: rs.merge, 106 } 107 } 108 109 type mergeState struct { 110 Commit string `json:"commit"` 111 PreMergeWorking string `json:"working_pre_merge"` 112 } 113 114 func (rs *repoStateLegacy) toRepoState() *RepoState { 115 newRS := &RepoState{ 116 Head: rs.Head, 117 Remotes: rs.Remotes, 118 Backups: rs.Backups, 119 Branches: rs.Branches, 120 staged: rs.Staged, 121 working: rs.Working, 122 merge: rs.Merge, 123 } 124 125 if newRS.Remotes == nil { 126 newRS.Remotes = concurrentmap.New[string, Remote]() 127 } 128 if newRS.Backups == nil { 129 newRS.Backups = concurrentmap.New[string, Remote]() 130 } 131 if newRS.Branches == nil { 132 newRS.Branches = concurrentmap.New[string, BranchConfig]() 133 } 134 135 return newRS 136 } 137 138 func (rs *repoStateLegacy) save(fs filesys.ReadWriteFS) error { 139 data, err := json.MarshalIndent(rs, "", " ") 140 if err != nil { 141 return err 142 } 143 144 return fs.WriteFile(getRepoStateFile(), data, os.ModePerm) 145 } 146 147 // LoadRepoState parses the repo state file from the file system given 148 func LoadRepoState(fs filesys.ReadWriteFS) (*RepoState, error) { 149 path := getRepoStateFile() 150 data, err := fs.ReadFile(path) 151 152 if err != nil { 153 return nil, err 154 } 155 156 var repoState repoStateLegacy 157 err = json.Unmarshal(data, &repoState) 158 159 if err != nil { 160 return nil, err 161 } 162 163 return repoState.toRepoState(), nil 164 } 165 166 func CloneRepoState(fs filesys.ReadWriteFS, r Remote) (*RepoState, error) { 167 init := ref.NewBranchRef(DefaultInitBranch) // best effort 168 hashStr := hash.Hash{}.String() 169 remotes := concurrentmap.New[string, Remote]() 170 remotes.Set(r.Name, r) 171 rs := &RepoState{ 172 Head: ref.MarshalableRef{Ref: init}, 173 staged: hashStr, 174 working: hashStr, 175 Remotes: remotes, 176 Branches: concurrentmap.New[string, BranchConfig](), 177 Backups: concurrentmap.New[string, Remote](), 178 } 179 180 err := rs.Save(fs) 181 if err != nil { 182 return nil, err 183 } 184 185 return rs, nil 186 } 187 188 func CreateRepoState(fs filesys.ReadWriteFS, br string) (*RepoState, error) { 189 headRef, err := ref.Parse(br) 190 191 if err != nil { 192 return nil, err 193 } 194 195 rs := &RepoState{ 196 Head: ref.MarshalableRef{Ref: headRef}, 197 Remotes: concurrentmap.New[string, Remote](), 198 Branches: concurrentmap.New[string, BranchConfig](), 199 Backups: concurrentmap.New[string, Remote](), 200 } 201 202 err = rs.Save(fs) 203 204 if err != nil { 205 return nil, err 206 } 207 208 return rs, nil 209 } 210 211 // Save writes this repo state file to disk on the filesystem given 212 func (rs RepoState) Save(fs filesys.ReadWriteFS) error { 213 data, err := json.MarshalIndent(rs, "", " ") 214 if err != nil { 215 return err 216 } 217 218 return fs.WriteFile(getRepoStateFile(), data, os.ModePerm) 219 } 220 221 func (rs *RepoState) CWBHeadRef() ref.DoltRef { 222 return rs.Head.Ref 223 } 224 225 func (rs *RepoState) CWBHeadSpec() *doltdb.CommitSpec { 226 spec, _ := doltdb.NewCommitSpec("HEAD") 227 return spec 228 } 229 230 func (rs *RepoState) AddRemote(r Remote) { 231 rs.Remotes.Set(r.Name, r) 232 } 233 234 func (rs *RepoState) RemoveRemote(r Remote) { 235 rs.Remotes.Delete(r.Name) 236 } 237 238 func (rs *RepoState) AddBackup(r Remote) { 239 rs.Backups.Set(r.Name, r) 240 } 241 242 func (rs *RepoState) RemoveBackup(r Remote) { 243 rs.Backups.Delete(r.Name) 244 }