github.com/shved/got@v0.0.0-20230322140632-a4bfa1e99685/object/object.go (about) 1 // Package object includes all the functions and types related to the Got objects and operations on them. 2 package object 3 4 import ( 5 "compress/gzip" 6 "crypto/sha1" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "os" 11 "path" 12 "sort" 13 "strings" 14 "time" 15 16 "github.com/shved/got/got" 17 ) 18 19 type ObjectType int 20 21 const ( 22 Commit ObjectType = iota + 1 23 Tree 24 Blob 25 ) 26 27 // Object is a struct representation of a repo object. 28 type Object struct { 29 ObjType ObjectType 30 Parent *Object 31 Children []*Object 32 Name string 33 ParentPath string 34 Path string 35 ParentCommitHash string 36 CommitMessage string 37 HashString string 38 Timestamp time.Time 39 40 sha []byte 41 contentLines []string 42 gzipContent string 43 } 44 45 // Show returns a string with object content. 46 func Show(shaString string) string { 47 if exists(path.Join(got.CommitDirAbsPath(), shaString)) { 48 return objContent(path.Join(got.CommitDirAbsPath(), shaString)) 49 } 50 51 if exists(path.Join(got.TreeDirAbsPath(), shaString)) { 52 return objContent(path.Join(got.TreeDirAbsPath(), shaString)) 53 } 54 55 if exists(path.Join(got.TreeDirAbsPath(), shaString)) { 56 return objContent(path.Join(got.TreeDirAbsPath(), shaString)) 57 } 58 59 log.Fatal(got.ErrObjDoesNotExist) 60 panic("never reach") 61 } 62 63 // objContent reads object archive and returns only its contentw without gzip headers. 64 func objContent(p string) string { 65 res, _ := readArchive(p) 66 return string(res) 67 } 68 69 // RecRestoreFromObject recursively writes objects into files/folders making an object graph 70 // persisted in a worktree. 71 func (o *Object) RecRestoreFromObject(p string) { 72 switch o.ObjType { 73 case Commit: 74 for _, ch := range o.Children { 75 ch.RecRestoreFromObject(p) 76 } 77 case Tree: 78 treePath := path.Join(p, o.Name) 79 err := os.Mkdir(treePath, 0755) 80 if err != nil { 81 log.Fatal(err) 82 } 83 for _, ch := range o.Children { 84 ch.RecRestoreFromObject(treePath) 85 } 86 case Blob: 87 blobPath := path.Join(p, o.Name) 88 if err := ioutil.WriteFile(blobPath, []byte(o.gzipContent), 0644); err != nil { 89 log.Fatal(err) 90 } 91 } 92 } 93 94 // RecReadObject recursively reads objects archives and links them into an object graph. 95 func RecReadObject(t ObjectType, hashString string, parentObj *Object) *Object { 96 switch t { 97 case Commit: 98 oPath := path.Join(t.storePath(), hashString) 99 res, header := readArchive(oPath) 100 commit := &Object{ 101 ObjType: Commit, 102 Name: header.Name, 103 sha: []byte(hashString), 104 HashString: hashString, 105 Timestamp: header.ModTime, 106 CommitMessage: header.Comment, 107 } 108 children := parseObjContent(string(res)) 109 for _, child := range children { 110 if child.t != Commit { 111 commit.Children = append(commit.Children, RecReadObject(child.t, child.hashString, commit)) 112 } else { 113 continue // skip parent commit entry in commit content 114 } 115 } 116 return commit 117 case Tree: 118 oPath := path.Join(t.storePath(), hashString) 119 res, header := readArchive(oPath) 120 tree := &Object{ 121 ObjType: Tree, 122 Name: header.Name, 123 sha: []byte(hashString), 124 HashString: hashString, 125 Parent: parentObj, 126 Timestamp: header.ModTime, 127 } 128 children := parseObjContent(string(res)) 129 for _, child := range children { 130 tree.Children = append(tree.Children, RecReadObject(child.t, child.hashString, tree)) 131 } 132 return tree 133 case Blob: 134 oPath := path.Join(t.storePath(), hashString) 135 res, header := readArchive(oPath) 136 blob := &Object{ 137 ObjType: Blob, 138 Name: header.Name, 139 sha: []byte(hashString), 140 HashString: hashString, 141 Parent: parentObj, 142 gzipContent: string(res), 143 Timestamp: header.ModTime, 144 } 145 return blob 146 default: 147 log.Fatalf("RecReadObject(): %v", got.ErrInvalidObjType) 148 } 149 panic("never reach") 150 } 151 152 // objRepr is a local type to proceed object string representation for the further transformation intro and object. 153 type objRepr struct { 154 t ObjectType 155 hashString string 156 name string 157 } 158 159 // parseObjContent takes object (commit or tree) contents and returns a slice of containing objects 160 // in a special representation form. 161 func parseObjContent(s string) []objRepr { 162 var objects []objRepr 163 lines := strings.Split(s, "\n") 164 for _, line := range lines { 165 objects = append(objects, parseObjString(line)) 166 } 167 return objects 168 } 169 170 // parseObjString parses object string representation. 171 func parseObjString(s string) objRepr { 172 entries := strings.Split(s, "\t") 173 var name string 174 if len(entries) > 3 { 175 name = entries[2] 176 } 177 return objRepr{t: strToObjType(entries[0]), hashString: entries[1], name: name} 178 } 179 180 // storePath returns objects path to write into depending on its type. 181 func (t ObjectType) storePath() string { 182 switch t { 183 case Commit: 184 return got.CommitDirAbsPath() 185 case Tree: 186 return got.TreeDirAbsPath() 187 case Blob: 188 return got.BlobDirAbsPath() 189 default: 190 log.Fatalf("storePath(): %v", got.ErrInvalidObjType) 191 } 192 panic("never reach") 193 } 194 195 // LogEntry function returns a string representation of a commit for repo commit log. 196 func (o *Object) LogEntry() string { 197 if o.ObjType != Commit { 198 log.Fatal(got.ErrWrongLogEntryType) 199 } 200 201 logEntry := strings.Join( 202 []string{ 203 o.Timestamp.UTC().Format(time.RFC3339), 204 o.HashString, 205 o.ParentCommitHash, 206 o.CommitMessage, 207 }, 208 "\t", 209 ) 210 return logEntry + "\n" 211 } 212 213 // RecCalcHashSum recursively calculates all objects sha1 in an object graph started from very far children 214 // and puts it into the object struct fields sha and HashString. 215 func (o *Object) RecCalcHashSum() { 216 switch o.ObjType { 217 case Commit: 218 for _, ch := range o.Children { 219 ch.RecCalcHashSum() 220 o.contentLines = append(o.contentLines, ch.buildContentLineForParent()) 221 } 222 if o.ParentCommitHash != string(got.EmptyCommitRef) { 223 parentCommitLine := parentCommitShaContentLine(o.ParentCommitHash) 224 o.contentLines = append(o.contentLines, parentCommitLine) 225 } 226 sort.Strings(o.contentLines) 227 o.gzipContent = strings.Join(o.contentLines, "\n") 228 data := []byte(o.gzipContent) 229 o.writeShaSum(data) 230 case Tree: 231 for _, ch := range o.Children { 232 ch.RecCalcHashSum() 233 o.contentLines = append(o.contentLines, ch.buildContentLineForParent()) 234 } 235 sort.Strings(o.contentLines) 236 o.gzipContent = strings.Join(o.contentLines, "\n") 237 data := []byte(o.gzipContent) 238 o.writeShaSum(data) 239 case Blob: 240 data, err := ioutil.ReadFile(o.Path) 241 if err != nil { 242 log.Fatal(err) 243 } 244 o.writeShaSum(data) 245 default: 246 log.Fatalf("RecCalcHashSum(): %v", got.ErrInvalidObjType) 247 } 248 } 249 250 // writeShaSum takes bytes data, calculates sha sum for it and writes sum and hash string into the object struct. 251 func (o *Object) writeShaSum(data []byte) { 252 h := sha1.New() 253 h.Write(data) 254 o.sha = h.Sum(nil) 255 o.HashString = hashString(o.sha) 256 } 257 258 // buildContentLineForParent builds a string to put into parents (commit or tree) content to be archived. 259 func (o *Object) buildContentLineForParent() string { 260 entries := []string{o.ObjType.toString(), o.HashString, o.Name} 261 return strings.Join(entries, "\t") 262 } 263 264 // parentCommitShaContentLine reads commit archive and builds content line for commit 265 // pointing to parent commit. 266 func parentCommitShaContentLine(parentHash string) string { 267 parentCommitPath := path.Join(got.CommitDirAbsPath(), parentHash) 268 fd, err := os.Open(parentCommitPath) 269 if err != nil { 270 log.Fatal(err) 271 } 272 unarchiver, _ := gzip.NewReader(fd) 273 defer fd.Close() 274 defer unarchiver.Close() 275 entries := []string{Commit.toString(), parentHash, unarchiver.Comment} 276 return strings.Join(entries, "\t") 277 } 278 279 // RecWriteObjects recursively writes archive for objects in a graph. 280 func (o *Object) RecWriteObjects() { 281 if o.ObjType == Commit || o.ObjType == Tree { 282 for _, ch := range o.Children { 283 ch.RecWriteObjects() 284 } 285 } 286 287 o.write() 288 } 289 290 // write function writes archives for objects. 291 func (o *Object) write() { 292 switch o.ObjType { 293 case Commit: 294 path := path.Join(got.CommitDirAbsPath(), o.HashString) 295 writeArchive(path, o.Name, []byte(o.gzipContent), time.Now(), o.CommitMessage) 296 got.UpdateHead(o.HashString) 297 case Tree: 298 path := path.Join(got.TreeDirAbsPath(), o.HashString) 299 if exists(path) { 300 break 301 } 302 writeArchive(path, o.Name, []byte(o.gzipContent), time.Now(), "") 303 case Blob: 304 path := path.Join(got.BlobDirAbsPath(), o.HashString) 305 if exists(path) { 306 break 307 } 308 data, err := ioutil.ReadFile(o.Path) 309 if err != nil { 310 log.Fatal(err) 311 } 312 writeArchive(path, o.Name, data, time.Now(), "") 313 default: 314 log.Fatalf("write(): %v", got.ErrInvalidObjType) 315 } 316 } 317 318 // writeArchive implements archive writing for object data. 319 func writeArchive(p string, name string, data []byte, t time.Time, commitMessage string) { 320 fd, _ := os.Create(p) 321 archiver := gzip.NewWriter(fd) 322 defer fd.Close() 323 defer archiver.Close() 324 archiver.Name = name 325 archiver.ModTime = t 326 if commitMessage != "" { 327 archiver.Comment = commitMessage 328 } 329 archiver.Write(data) 330 } 331 332 // readArchive reads a gzip archive and returns its content and header struct. 333 func readArchive(p string) ([]byte, gzip.Header) { 334 fd, err := os.Open(p) 335 unarchiver, err := gzip.NewReader(fd) 336 if err != nil { 337 log.Fatalf("reading archive %s: %v", p, err) 338 } 339 defer fd.Close() 340 defer unarchiver.Close() 341 res, err := ioutil.ReadAll(unarchiver) 342 if err != nil { 343 log.Fatalf("reading archive %s: %v", p, err) 344 } 345 return res, unarchiver.Header 346 } 347 348 // hashString converts hashSum into string representation. 349 func hashString(hashSum []byte) string { 350 return fmt.Sprintf("%x", hashSum) 351 } 352 353 // toString converts object type into its string representation. 354 func (t ObjectType) toString() string { 355 switch t { 356 case Commit: 357 return "commit" 358 case Tree: 359 return "tree" 360 case Blob: 361 return "blob" 362 default: 363 log.Fatalf("toString(): %v", got.ErrInvalidObjType) 364 panic("never reach") 365 } 366 } 367 368 // strToObjType converts string into respective object type. 369 func strToObjType(s string) ObjectType { 370 switch s { 371 case "commit": 372 return Commit 373 case "tree": 374 return Tree 375 case "blob": 376 return Blob 377 default: 378 log.Fatalf("strToObjType(): %v (%v)", got.ErrInvalidObjType, s) 379 panic("never reach") 380 } 381 } 382 383 // exists tests wheather a file exists. 384 func exists(path string) bool { 385 if _, err := os.Stat(path); err != nil { 386 if os.IsNotExist(err) { 387 return false 388 } 389 } 390 return true 391 }