// TakeSnapshot recursivelly snapshots the current directory. func TakeSnapshot(b *bolt.Bucket, filePath string) types.Hash { // Determine if path is a directory or a file info, err := os.Stat(filePath) if err != nil { log.Fatalln("Could not call os.Stat on " + filePath) } if info.IsDir() { // If the path is a directory, we need to create a tree object treeObject := types.NewTreeObject() // Enumerate all of the files in this directory files, err := ioutil.ReadDir(filePath) if err != nil { log.Fatalf("Could not list files in dir %s: %v", filePath, err) } for _, file := range files { if util.IgnorePath(file.Name()) { continue } // Snapshot the file, and add it to the current tree object hash := TakeSnapshot(b, path.Join(filePath, file.Name())) treeObject.AddFile(file.Name(), hash, file.IsDir()) } // Calculate hash of the directory serialized := treeObject.Serialize() hash := types.CalculateHash(serialized) // Put directory into database, addressed by it's hash b.Put(hash, serialized) util.DebugLog("Put directory \"" + filePath + "\" into database with hash " + hash.String()) return hash } // Calculate hash of file contents file, err := ioutil.ReadFile(filePath) if err != nil { panic(err.Error()) } hash := types.CalculateHash(file) // Put file into database, addressed by it's hash b.Put(hash, file) util.DebugLog("Put file \"" + filePath + "\" into database with hash " + hash.String()) return hash }
// TreeDiff lists the differences between a Tree object in a snapshot // and a filesystem path. func TreeDiff(objects *bolt.Bucket, treeHash types.Hash, dir string) []Difference { differences := []Difference{} // Try to list all of the files in this directory. files, listErr := ioutil.ReadDir(dir) // Try to load in the tree object. var treeObject *types.TreeObject = nil if !treeHash.Equal(types.EMPTY) { treeObject = types.DeserializeTreeObject(objects.Get(treeHash)) } // For each file in the current directory, determine if the file was either added or modified. if listErr == nil { for _, file := range files { if util.IgnorePath(file.Name()) { continue } if file.IsDir() { if treeObject == nil { differences = append(differences, TreeDiff(objects, types.EMPTY, path.Join(dir, file.Name()))...) } else { if treeObject.HasFile(file.Name()) { differences = append(differences, TreeDiff(objects, treeObject.GetFile(file.Name()), path.Join(dir, file.Name()))...) } else { differences = append(differences, TreeDiff(objects, types.EMPTY, path.Join(dir, file.Name()))...) } } } else { if treeObject == nil { differences = append(differences, Difference{ Type: "A", FilePath: path.Join(dir, file.Name()), }) } else { if treeObject.HasFile(file.Name()) { fileBytes, err := ioutil.ReadFile(path.Join(dir, file.Name())) if err != nil { panic(err) } if !types.CalculateHash(fileBytes).Equal(treeObject.GetFile(file.Name())) { differences = append(differences, Difference{ Type: "M", FilePath: path.Join(dir, file.Name()), }) } } else { differences = append(differences, Difference{ Type: "A", FilePath: path.Join(dir, file.Name()), }) } } } } } // For each file in the the tree object, see if that file was removed in the working directory if treeObject != nil { for _, entry := range treeObject.Files { if !listHasFile(files, entry.Name) { if entry.IsDir { differences = append(differences, TreeDiff(objects, entry.Hash, path.Join(dir, entry.Name))...) } else { differences = append(differences, Difference{ Type: "R", FilePath: path.Join(dir, entry.Name), }) } } } } return differences }
// Commit implements `got commit` func Commit(c *cli.Context) { db := util.OpenDB() defer db.Close() if len(c.String("message")) == 0 { log.Fatalln("Must supply a commit message.") } if len(c.String("author")) == 0 { log.Fatalln("Must supply a commit author.") } // Perform operations in a write lock err := db.Update(func(tx *bolt.Tx) error { info := tx.Bucket(util.INFO) // The bucket holding repositry metadata objects := tx.Bucket(util.OBJECTS) // The bucket holding got objects current := info.Get(util.CURRENT) // Create a commit object from the current directory snapshot util.DebugLog("Taking snapshot of repo...") hash := TakeSnapshot(tx.Bucket(util.OBJECTS), ".") util.DebugLog("Repo snapshot has hash " + hash.String() + ".") commit := types.CommitObject{ Author: c.String("author"), Message: c.String("message"), Time: time.Now(), Tree: hash, Parents: make([]types.Hash, 0), } // There is a 'current' commit, then it is the parent of the new commit if current != nil { commit.Parents = append(commit.Parents, current) } commitBytes := commit.Serialize() commitSha := types.CalculateHash(commitBytes) objects.Put(commitSha, commitBytes) util.DebugLog("Created commit of sha " + commitSha.String() + ".") // CURRENT now corresponds to the commit that we just created info.Put(util.CURRENT, commitSha) // Update heads headsBytes := info.Get(util.HEADS) if headsBytes != nil { heads := types.DeserializeHashes(headsBytes) util.DebugLog("Currently " + strconv.Itoa(len(heads)) + " heads in this repo...") // Remove current, if it is a head for i, head := range heads { if head.Equal(current) { heads = append(heads[:i], heads[i+1:]...) break } } // Add the new commit as a head heads = append(heads, commitSha) info.Put(util.HEADS, types.SerializeHashes(heads)) } else { util.DebugLog("Currently no heads in this repo. Putting HEADS: " + fmt.Sprint(([]types.Hash{commitSha}))) info.Put(util.HEADS, types.SerializeHashes([]types.Hash{commitSha})) } return nil }) if err != nil { log.Fatal("Error reading from the database.") } }