Created
March 18, 2019 05:43
-
-
Save umurgdk/04a6fedc2595706cf2d9c7148912d138 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"crypto/sha1" | |
"encoding/gob" | |
"flag" | |
"fmt" | |
"io/ioutil" | |
"os" | |
"path" | |
"path/filepath" | |
"sync" | |
"time" | |
) | |
type Context struct { | |
dir string | |
HEAD string | |
Perm os.FileMode | |
} | |
type Object struct { | |
Typ string | |
Data []byte | |
} | |
type Commit struct { | |
Parent string | |
Author string | |
CreatedAt time.Time | |
Index []string | |
} | |
type Index []string | |
func NewContext(dir string) *Context { | |
head, _ := ioutil.ReadFile(filepath.Join(dir, "HEAD")) | |
return &Context{dir, string(head), 0640} | |
} | |
func (c *Context) objsDir() string { | |
return path.Join(c.dir, "objects") | |
} | |
func (c *Context) PutObject(typ string, data []byte) (string, error) { | |
var buf bytes.Buffer | |
enc := gob.NewEncoder(&buf) | |
if err := enc.Encode(Object{typ, data}); err != nil { | |
return "", err | |
} | |
sum := sha1.Sum(buf.Bytes()) | |
hash := fmt.Sprintf("%x", sum) | |
fileName := filepath.Join(c.objsDir(), hash) | |
err := ioutil.WriteFile(fileName, buf.Bytes(), c.Perm) | |
return hash, err | |
} | |
func (c *Context) ReadObject(hash string) (Object, error) { | |
filename := filepath.Join(c.objsDir(), hash) | |
file, err := os.Open(filename) | |
if err != nil { | |
return Object{}, err | |
} | |
defer file.Close() | |
var obj Object | |
dec := gob.NewDecoder(file) | |
if err := dec.Decode(&obj); err != nil { | |
return Object{}, err | |
} | |
return obj, nil | |
} | |
func (c *Context) PutCommit(commit Commit) (string, error) { | |
var buf bytes.Buffer | |
encoder := gob.NewEncoder(&buf) | |
if err := encoder.Encode(commit); err != nil { | |
return "", err | |
} | |
return c.PutObject("commit", buf.Bytes()) | |
} | |
func (c *Context) ReadCommit(hash string) (Commit, error) { | |
obj, err := c.ReadObject(hash) | |
if err != nil { | |
return Commit{}, err | |
} | |
if obj.Typ != "commit" { | |
return Commit{}, fmt.Errorf("invalid blob: %s, expected commit, got %s", hash, obj.Typ) | |
} | |
return Obj2Commit(obj) | |
} | |
func (c *Context) WriteHead(hash string) error { | |
fileName := filepath.Join(c.dir, "HEAD") | |
if err := ioutil.WriteFile(fileName, []byte(hash), c.Perm); err != nil { | |
return err | |
} | |
c.HEAD = hash | |
return nil | |
} | |
func (c *Context) History() History { | |
return History{ | |
ctx: c, | |
Hash: c.HEAD, | |
Commit: Commit{Parent: c.HEAD}, | |
Error: nil, | |
} | |
} | |
func Obj2Commit(obj Object) (Commit, error) { | |
var commit Commit | |
err := gob.NewDecoder(bytes.NewReader(obj.Data)).Decode(&commit) | |
return commit, err | |
} | |
type History struct { | |
ctx *Context | |
Hash string | |
Commit Commit | |
Error error | |
} | |
func (h *History) Parent() bool { | |
if h.Error != nil { | |
return false | |
} | |
if h.Commit.Parent == "" { | |
return false | |
} | |
parent, err := h.ctx.ReadCommit(h.Commit.Parent) | |
if err != nil { | |
h.Error = err | |
return false | |
} | |
h.Hash = h.Commit.Parent | |
h.Commit = parent | |
return true | |
} | |
func printUsage() { | |
fmt.Printf(`Usage: %s task [arguments] | |
Tasks: | |
log Print the history | |
put <type> <path, [path]> Puts a file to the storage | |
read <hash> Read an object | |
commit <author> <hash,[hash]> Commits given list of hashes`, os.Args[0]) | |
println() | |
} | |
func main() { | |
flag.Usage = printUsage | |
flag.Parse() | |
if flag.NArg() == 0 { | |
flag.Usage() | |
os.Exit(-1) | |
} | |
os.MkdirAll("_dolap/objects", 0750) | |
ctx := NewContext("_dolap") | |
var err error | |
switch flag.Arg(0) { | |
case "log": | |
err = cmdLog(ctx) | |
case "put": | |
if flag.NArg() < 3 { | |
flag.Usage() | |
os.Exit(-1) | |
} | |
err = cmdPut(ctx, flag.Arg(1), flag.Args()[2:]) | |
case "read": | |
if flag.NArg() != 2 { | |
flag.Usage() | |
os.Exit(-1) | |
} | |
err = cmdRead(ctx, flag.Arg(1)) | |
case "commit": | |
if flag.NArg() < 3 { | |
flag.Usage() | |
os.Exit(-1) | |
} | |
err = cmdCommit(ctx, flag.Arg(1), flag.Args()[2:]) | |
} | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "%v\n", err) | |
os.Exit(1) | |
} | |
} | |
func cmdPut(ctx *Context, typ string, files []string) error { | |
errors := make(chan error, len(files)) | |
fileContents := make([][]byte, len(files)) | |
var wg sync.WaitGroup | |
for i, filename := range files { | |
wg.Add(1) | |
go func(i int, filename string) { | |
defer wg.Done() | |
bytes, err := ioutil.ReadFile(filename) | |
if err != nil { | |
errors <- err | |
return | |
} | |
fileContents[i] = bytes | |
}(i, filename) | |
} | |
wg.Wait() | |
close(errors) | |
if err := <-errors; err != nil { | |
return err | |
} | |
wg = sync.WaitGroup{} | |
wg.Add(len(fileContents)) | |
errors = make(chan error, len(fileContents)) | |
hashes := make([]string, len(fileContents)) | |
for i, fileContent := range fileContents { | |
go func(i int, fileContent []byte) { | |
defer wg.Done() | |
hash, err := ctx.PutObject(typ, fileContent) | |
if err != nil { | |
errors <- err | |
return | |
} | |
hashes[i] = hash | |
}(i, fileContent) | |
} | |
wg.Wait() | |
close(errors) | |
if err := <-errors; err != nil { | |
return err | |
} | |
for _, hash := range hashes { | |
println(hash) | |
} | |
return nil | |
} | |
func cmdRead(ctx *Context, hash string) error { | |
obj, err := ctx.ReadObject(hash) | |
if err != nil { | |
return err | |
} | |
switch obj.Typ { | |
case "commit": | |
commit, err := Obj2Commit(obj) | |
if err != nil { | |
return err | |
} | |
fmt.Printf("%s %v %s\n", commit.Author, commit.CreatedAt, hash) | |
fmt.Printf("Parent: %s\n", commit.Parent) | |
fmt.Printf("Files:\n") | |
for _, blobHash := range commit.Index { | |
fmt.Printf("* %s\n", blobHash) | |
} | |
default: | |
fmt.Printf("Type: %s\nData: %s\n", obj.Typ, string(obj.Data)) | |
} | |
return nil | |
} | |
func cmdCommit(ctx *Context, author string, hashes []string) error { | |
commit := Commit{ | |
Author: author, | |
CreatedAt: time.Now(), | |
Index: hashes, | |
Parent: ctx.HEAD, | |
} | |
hash, err := ctx.PutCommit(commit) | |
if err != nil { | |
return err | |
} | |
if err := ctx.WriteHead(hash); err != nil { | |
return err | |
} | |
println(hash) | |
return nil | |
} | |
func cmdLog(ctx *Context) error { | |
history := ctx.History() | |
for history.Parent() { | |
fmt.Printf("%s %v %s\n", history.Commit.Author, history.Commit.CreatedAt, history.Hash) | |
} | |
return history.Error | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment