// Revert implements got revert func Revert(c *cli.Context) { // Open the database db := util.OpenDB() defer db.Close() // Perform operations in a read-only lock err := db.View(func(tx *bolt.Tx) error { // Get the current commit sha info := tx.Bucket(util.INFO) objects := tx.Bucket(util.OBJECTS) current := info.Get(util.CURRENT) // Get the commit object and associated tree commit := types.DeserializeCommitObject(objects.Get(current)) tree := types.DeserializeTreeObject(objects.Get(commit.Tree)) for _, file := range c.Args() { fmt.Println("Reverting " + file + "...") fileData := TreeLookup(objects, tree, path.Clean(file)) ioutil.WriteFile(file, fileData, 0644) } return nil }) if err != nil { log.Fatal("Error reading from the database.") } }
// Status implements the `got status` command. func Status(c *cli.Context) { // Open the database db := util.OpenDB() defer db.Close() // Colored output yellow := ansi.ColorFunc("yellow+h:black") green := ansi.ColorFunc("green+h:black") red := ansi.ColorFunc("red+h:black") // Perform operations in a read-only lock err := db.View(func(tx *bolt.Tx) error { // Get the current commit sha info := tx.Bucket(util.INFO) objects := tx.Bucket(util.OBJECTS) current := info.Get(util.CURRENT) // Find the differences between the working directory and the tree of the current commit. differences := []Difference{} if current != nil { // Load commit object commit := types.DeserializeCommitObject(objects.Get(current)) util.DebugLog("Comparing working directory to commit '" + commit.Message + "'.") differences = TreeDiff(objects, commit.Tree, ".") } else { // Compare directory to the empty hash util.DebugLog("Comparing working directory to empty tree.") differences = TreeDiff(objects, types.EMPTY, ".") } // Print out the found differences for _, difference := range differences { line := fmt.Sprintf("%s %s", difference.Type, difference.FilePath) if difference.Type == "A" { fmt.Println(green(line)) } if difference.Type == "R" { fmt.Println(red(line)) } if difference.Type == "M" { fmt.Println(yellow(line)) } } return nil }) if err != nil { log.Fatal("Error reading from the database.") } }
func Log(c *cli.Context) { db := util.OpenDB() defer db.Close() yellow := ansi.ColorFunc("yellow+h:black") // Perform operations in a read-only lock db.View(func(tx *bolt.Tx) error { info := tx.Bucket(util.INFO) objects := tx.Bucket(util.OBJECTS) headsBytes := info.Get(util.HEADS) if headsBytes != nil { queue := types.DeserializeHashes(headsBytes) // TODO: Keep a visited set, so we don't repeat commits for len(queue) > 0 { i := GetNewestCommit(objects, queue) // Get commit and remove it from the priority queue commitSha := queue[i] commit := types.DeserializeCommitObject(objects.Get(commitSha)) queue = append(queue[:i], queue[i+1:]...) fmt.Printf(yellow("commit %s\n"), commitSha) fmt.Printf("Message: %s\n", commit.Message) fmt.Printf("Author: %s\n", commit.Author) fmt.Printf("Date: %s\n", commit.Time) if len(commit.Parents) > 0 { fmt.Printf("Parents: %s\n", commit.Parents) } fmt.Printf("Tree: %s\n", commit.Tree) fmt.Println() // Append parents of this commit to the queue queue = append(queue, commit.Parents...) } return nil } fmt.Println("There are no commits in this repository...") return nil }) }
// Application entry point func main() { app := cli.NewApp() app.Name = "got" app.Usage = "A VCS written in golang." app.Commands = []cli.Command{ { Name: "init", Usage: "Create an empty got repository in the current directory.", Action: func(c *cli.Context) { if _, err := os.Stat(util.DBName); err == nil { fmt.Printf("Got repository already exists in this folder.\n") } else { db, err := bolt.Open(util.DBName, 0600, nil) if err != nil { log.Fatal("Could not create " + util.DBName + "\n") } db.Update(func(tx *bolt.Tx) error { tx.CreateBucket(util.INFO) tx.CreateBucket(util.OBJECTS) return nil }) defer db.Close() } }, }, { Name: "log", Usage: "Show commit logs.", Action: commands.Log, }, { Name: "status", Usage: "Diff the current working directory with the last commit.", Action: commands.Status, }, { Name: "commit", Usage: "Record changes to the repository.", Flags: []cli.Flag{ cli.StringFlag{ Name: "message, m", Usage: "Commit message", }, cli.StringFlag{ Name: "author, a", Usage: "Commit author", }, }, Action: commands.Commit, }, { Name: "checkout", Usage: "Checkout a the working tree to a commit.", Action: func(c *cli.Context) { db := util.OpenDB() defer db.Close() }, }, { Name: "revert", Usage: "Revert a file to the last commit.", Action: commands.Revert, }, } app.Run(os.Args) }
// 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.") } }