Shareable Git-powered notebooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

222 lines
4.3 KiB

package main
import (
"os/exec"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/pkg/errors"
)
/*n, err := loadNotebook("/home/trevor/stick/personal/")
if err != nil {
panic(err)
}
log.Print(time.Unix(n.created(), 0))
log.Print(time.Unix(n.Modified(), 0))
err = n.upload()
if err != nil {
if strings.Contains(err.Error(), git.ErrNonFastForwardUpdate.Error()) {
log.Println("conflict while pushing")
} else if err != git.NoErrAlreadyUpToDate {
panic(err)
}
}
err = n.download()
if err != nil {
if strings.Contains(err.Error(), git.ErrNonFastForwardUpdate.Error()) {
log.Println("conflict while updating")
log.Println(err)
} else if err != git.NoErrAlreadyUpToDate {
panic(err)
}
}*/
const defaultSubmoduleDepth = 10
type notebook struct {
ID string
Label string
Serve map[string]int `json:"-"`
Repository *git.Repository `json:"-"`
}
func (n *notebook) getNote(id string, fetchBody bool) *note {
file := n.file(id)
if file == nil {
return nil
}
r := n.Repository
ref, err := r.Head()
if err != nil {
return nil // Empty repository
}
cIter, err := r.Log(&git.LogOptions{From: ref.Hash(), Order: git.LogOrderCommitterTime, FileName: &file.Name})
checkError(err)
note := &note{ID: id}
err = cIter.ForEach(func(c *object.Commit) error {
note.ModifiedAt = c.Author.When.Unix()
note.ModifiedBy = c.Author.Name
return storer.ErrStop
})
checkError(err)
note.Label = file.Name
if strings.HasSuffix(strings.ToLower(note.Label), ".md") {
note.Label = note.Label[0 : len(note.Label)-3]
}
note.Label = strings.Title(strings.Replace(note.Label, "-", " ", -1))
if fetchBody {
note.Body, err = file.Contents()
if err != nil {
return nil
}
}
return note
}
func (n *notebook) allNotes() map[string]*note {
r := n.Repository
ref, err := r.Head()
checkError(err)
commit, err := r.CommitObject(ref.Hash())
checkError(err)
tree, err := commit.Tree()
checkError(err)
notes := make(map[string]*note)
err = tree.Files().ForEach(func(f *object.File) error {
noteID := hash(f.Name)
note := n.getNote(noteID, true)
notes[noteID] = note
return nil
})
checkError(err)
return notes
}
type servedNotebook struct {
*notebook
Notes map[string]*note
}
func newNotebook(worktree string, cloneurl string) (*notebook, error) {
var err error
n := &notebook{}
n.Repository, err = initializeRepository(worktree, cloneurl)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize repository")
}
return n, nil
}
func loadNotebook(worktree string) (*notebook, error) {
var err error
n := &notebook{}
n.Repository, err = loadRepository(worktree)
if err != nil {
return nil, errors.Wrap(err, "failed to load repository")
}
return n, nil
}
func (n *notebook) created() int64 {
head, err := n.Repository.Head()
if err != nil {
return 0 // No commits yet
}
commit, err := n.Repository.CommitObject(head.Hash())
if err != nil {
return 0
}
for {
numParents := commit.NumParents()
if numParents == 0 {
break
}
commit, err = commit.Parent(numParents - 1)
if err != nil {
return 0
}
}
return commit.Author.When.Unix()
}
func (n *notebook) download() error {
w, err := n.Repository.Worktree()
if err != nil {
return errors.Wrap(err, "failed to read worktree")
}
return w.Pull(&git.PullOptions{RemoteName: "origin", RecurseSubmodules: defaultSubmoduleDepth})
}
func (n *notebook) upload() error {
return n.Repository.Push(&git.PushOptions{RemoteName: "origin"})
}
func (n *notebook) file(id string) *object.File {
var file *object.File
ref, err := n.Repository.Head()
if err != nil {
return nil // Empty repository
}
commit, err := n.Repository.CommitObject(ref.Hash())
checkError(err)
tree, err := commit.Tree()
checkError(err)
err = tree.Files().ForEach(func(f *object.File) error {
if hash(f.Name) == id {
file = f
return storer.ErrStop
}
return nil
})
checkError(err)
return file
}
func (n *notebook) compact() {
tree, err := n.Repository.Worktree()
checkError(err)
wtroot := tree.Filesystem.Root()
cmd := exec.Command("git", "-C", tree.Filesystem.Root(), "gc", "--aggressive", "--prune=now")
err = cmd.Run()
checkError(err)
n.Repository, err = loadRepository(wtroot)
checkError(err)
}