@@ -12,13 +12,9 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"strings"
"time"

"github.com/golang/glog"
git "github.com/libgit2/git2go"
)

const debug = true
@@ -55,20 +51,6 @@ func assertExists(path ...string) (e error) {

type Database struct {
Path string
Repo *git.Repository
}

var pw2Signature = git.Signature{
Name: "pw2",
Email: gpgUserEmail,
}

func gitSignature() (s *git.Signature) {
so := pw2Signature
so.When = time.Now()
s = &so

return
}

type cmdLogWriter struct {
@@ -103,7 +85,7 @@ func cmd(command string, arguments ...string) (c *exec.Cmd) {
return
}

func (d *Database) GenerateWebGPGUser(password string) (err error) {
func (d *Database) GenerateWebGPGUser(password []byte) (err error) {
f, err := ioutil.TempFile("", "pw2")
if err != nil {
return
@@ -113,14 +95,14 @@ func (d *Database) GenerateWebGPGUser(password string) (err error) {
err = os.Remove(f.Name())
}()

if _, err = f.WriteString(`
if _, err = fmt.Fprintf(f, `
%echo generating web interface gpg key...
Key-Type: RSA
Key-Length: 2048
Name-Real: pw2
Name-Email: ` + gpgUserEmail + `
Name-Email: `+gpgUserEmail+`
Name-Comment: this is the GPG key for the PW2 web interface
Passphrase: ` + password); err != nil {
Passphrase: %s`, password); err != nil {
return
}

@@ -143,259 +125,66 @@ func (d *Database) BlackboxCommand(command string, params ...string) (c *exec.Cm
return
}

func (d *Database) commitIndex(message string) (oid *git.Oid, err error) {

glog.V(2).Infof("~ git commit -m %+q", message)

// yes, this is really how the library is meant to be used :(
// https://github.com/libgit2/libgit2/blob/091165c53b2bcd5d41fb71d43ed5a23a3d96bf5d/tests/object/commit/commitstagedfile.c#L21-L134

var index *git.Index
if index, err = d.Repo.Index(); err != nil {
return
}

defer index.Free()

if glog.V(2) {
glog.Infof("%v files in index", index.EntryCount())
}

var treeOid *git.Oid
if treeOid, err = index.WriteTree(); err != nil {
return
}

var tree *git.Tree
if tree, err = d.Repo.LookupTree(treeOid); err != nil {
return
}

var lastCommit *git.Commit
if lastCommit, err = d.LastCommit(); err != nil {
return
}

sig := gitSignature()

switch lastCommit {
case nil:
if oid, err = d.Repo.CreateCommit(
"HEAD",
sig,
sig,
message,
tree,
); err != nil {
return
}
default:
defer lastCommit.Free()
if oid, err = d.Repo.CreateCommit(
"HEAD",
sig,
sig,
message,
tree,
lastCommit,
); err != nil {
return
}
}

return

}

func (d *Database) LastCommit() (c *git.Commit, err error) {
// look up last commit
var head *git.Reference
if head, err = d.Repo.Head(); err != nil {
fmt.Println(reflect.TypeOf(err))
if gE, ok := err.(*git.GitError); ok && gE.Code == git.ErrUnbornBranch {
err = nil
}

return

}

if c, err = d.Repo.LookupCommit(head.Target()); err != nil {
return
}

return
}

func findGlob(base string, dirRecurse bool, pattern string) (matches []string, err error) {
ms, err := filepath.Glob(filepath.Join(base, pattern))
if err != nil {
return
}

for _, m := range ms {
if dirRecurse {
var inf os.FileInfo
inf, err = os.Stat(m)
if err != nil {
return
}

if inf.IsDir() {
var dirMatches []string
var rel string

if rel, err = filepath.Rel(base, m); err != nil {
return
}

dirMatches, err = findGlob(base, dirRecurse, filepath.Join(rel, "*"))
if err != nil {
return
}

matches = append(matches, dirMatches...)
continue
}
}

m, err = filepath.Rel(base, m)
if err != nil {
return
}

matches = append(matches, m)
}

return
}

type ErrUnmatched []string

func (e ErrUnmatched) Error() string {
return fmt.Sprintf("expected %s to match files", strings.Join([]string(e), " "))
}

func findAllGlob(base string, mustMatchAll, dirRecurse bool, pattern ...string) (matches []string, err error) {
var unmatchedPattern ErrUnmatched = make([]string, 0, len(pattern))
for _, p := range pattern {
var ms []string
ms, err = findGlob(base, dirRecurse, p)
if err != nil {
return
}
func (d *Database) gitCmd(arguments ...string) (c *exec.Cmd) {
c = cmd("git", arguments...)

if mustMatchAll && len(ms) < 1 {
unmatchedPattern = append(unmatchedPattern, p)
}

matches = append(matches, ms...)
}

if len(unmatchedPattern) > 0 {
err = unmatchedPattern
}

return
}

func (d *Database) addFilesGlob(patterns ...string) (err error) {
files, err := findAllGlob("git", true, true, patterns...)
if err != nil {
return
}

return d.addFiles(files...)
}

func (d *Database) addFiles(files ...string) (err error) {
glog.V(2).Infof("~ git add %s", strings.Join(files, " "))
var index *git.Index
if index, err = d.Repo.Index(); err != nil {
return
}

for _, f := range files {
if err = index.AddByPath(f); err != nil {
return
}
}
c.Dir = d.Path

return
}

func Create(location string, webPassword string) (d Database, err error) {
func Create(location string, webPassword []byte) (d Database, err error) {
d.Path = location
// make sure the git repo folder exists
if err = os.Mkdir(location, defaultMode); err != nil {
return
}

// make the actual git repo
if d.Repo, err = git.InitRepository(location /*🐻*/, false); err != nil {
return
}

// add the blackbox submodule
var md *git.Submodule
if md, err = d.Repo.Submodules.Add(blackboxSubmoduleLocation, "blackbox", false); err != nil {
return
}

// get the repo of the new submodule
var mdRepo *git.Repository
if mdRepo, err = md.Open(); err != nil {
return
}

// get the latest version of blackbox from the internart
if err = detachedHeadPull(mdRepo); err != nil {
return
}

// write the submodule to the superproject index
if err = md.AddToIndex(true); err != nil {
if err = d.gitCmd("init").Run(); err != nil {
return
}

if err = d.addFiles(".gitmodules"); err != nil {
if err = d.gitCmd("submodule", "add", blackboxSubmoduleLocation, "blackbox").Run(); err != nil {
return
}

if _, err = d.commitIndex("add blackbox submodule"); err != nil {
if err = d.gitCmd("commit", "-m", "add blackbox submodule").Run(); err != nil {
return
}

if err = d.BlackboxCommand("initialize", "yes").Run(); err != nil {
return
}

if err = d.addFilesGlob("keyrings", ".gitignore"); err != nil {
if err = d.gitCmd("add", "--", "keyrings", ".gitignore").Run(); err != nil {
return
}

if _, err = d.commitIndex("🔒 initialize blackbox 🔒"); err != nil {
if err = d.gitCmd("commit", "-m", "🔒 initialize blackbox 🔒").Run(); err != nil {
return
}

if webPassword != "" {
if webPassword == nil {
if err = d.GenerateWebGPGUser(webPassword); err != nil {
return
}

webPassword = nil

if err = d.BlackboxCommand("addadmin", gpgUserEmail, path.Join("..", gpgHomedir)).
Run(); err != nil {
return
}

if err = d.addFiles(
if err = d.gitCmd("add", "--",
"keyrings/live/pubring.kbx",
"keyrings/live/trustdb.gpg",
"keyrings/live/blackbox-admins.txt"); err != nil {
"keyrings/live/blackbox-admins.txt").Run(); err != nil {
return
}

if _, err = d.commitIndex("+ add web client as blackbox admin"); err != nil {
if err = d.gitCmd("commit", "-m", "+ add web client as blackbox admin").Run(); err != nil {
return
}

@@ -404,62 +193,10 @@ func Create(location string, webPassword string) (d Database, err error) {
return
}

func detachedHeadPull(r *git.Repository) (err error) {
glog.Info("pulling remote...")
var origin *git.Remote
if origin, err = r.Remotes.Lookup("origin"); err != nil {
return
}

if err = origin.Fetch([]string{}, &git.FetchOptions{}, ""); err != nil {
return
}

if err = r.SetHead("refs/remotes/origin/master"); err != nil {
return
}

var ref *git.Reference
if ref, err = r.Head(); err != nil {
return
}

var commit *git.Commit
if commit, err = r.LookupCommit(ref.Target()); err != nil {
return
}

if err = r.ResetToCommit(commit, git.ResetHard, &git.CheckoutOpts{
Strategy: git.CheckoutForce,
DirMode: 0700,
FileMode: 0700,
}); err != nil {
return
}

glog.Info("remote pull complete")

return

}

//Func DatabaseNotFound returns true if err is a git2go not found error ("object not found").
//This error would be returned for example when you're trying to load a database
//from a directory which doesn't yet exist.
func DatabaseNotFound(err error) bool {
v, ok := err.(*git.GitError)

return ok && v.Code == git.ErrNotFound
}

//Func open opens a directory as a pw2 Database.
func Open(location string) (d Database, err error) {
d.Path = location

if d.Repo, err = git.OpenRepository(location); err != nil {
return
}

return
}