Commit 3aedc246 authored by Kirill Smelkov's avatar Kirill Smelkov

Move error-handling routines & co to lab.nexedi.com/kirr/go123

error.go is completely being moved to that shared place for handy Go
utilities into several subpackages:

	lab.nexedi.com/kirr/go123/exc		-- exception-style error handling for Go
	lab.nexedi.com/kirr/go123/myname	-- easy way to determine current function's name and package
	lab.nexedi.com/kirr/go123/xerr		-- addons for error-handling
	lab.nexedi.com/kirr/go123/xruntime	-- addons to standard package runtime
parent 3ba6cf73
// Copyright (C) 2015-2016 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// Git-backup | Exception-style errors
package main
import (
"fmt"
"runtime"
"strings"
)
// error type which is raised by raise(arg)
type Error struct {
arg interface{}
link *Error // chain of linked Error(s) - see e.g. errcontext()
}
func (e *Error) Error() string {
msgv := []string{}
msg := ""
for e != nil {
// TODO(go1.7) -> runtime.Frame (see xtraceback())
if f, ok := e.arg.(Frame); ok {
//msg = f.Function
//msg = fmt.Sprintf("%s (%s:%d)", f.Function, f.File, f.Line)
msg = strings.TrimPrefix(f.Name(), _errorpkgdot) // XXX -> better prettyfunc
} else {
msg = fmt.Sprint(e.arg)
}
msgv = append(msgv, msg)
e = e.link
}
return strings.Join(msgv, ": ")
}
// turn any value into Error
// if v is already Error - it stays the same
// otherwise new Error is created
func aserror(v interface{}) *Error {
if e, ok := v.(*Error); ok {
return e
}
return &Error{v, nil}
}
// raise error to upper level
func raise(arg interface{}) {
panic(aserror(arg))
}
// raise formatted string
func raisef(format string, a ...interface{}) {
panic(aserror(fmt.Sprintf(format, a...)))
}
// raise if err != nil
// NOTE err can be != nil even if typed obj = nil:
// var obj *T;
// err = obj
// err != nil is true
func raiseif(err error) {
//if err != nil && !reflect.ValueOf(err).IsNil() {
if err != nil {
panic(aserror(err))
}
}
// checks recovered value to be of *Error
// if there is non-Error error - repanic it
// otherwise return Error either nil (no panic), or actual value
func _errcatch(r interface{}) *Error {
e, _ := r.(*Error)
if e == nil && r != nil {
panic(r)
}
return e
}
// catch error and call f(e) if it was caught.
// must be called under defer
func errcatch(f func(e *Error)) {
e := _errcatch(recover())
if e == nil {
return
}
f(e)
}
// be notified when error unwinding is being happening.
// hook into unwinding process with f() call. Returned error is reraised.
// see also: errcontext()
// must be called under defer
func erronunwind(f func(e *Error) *Error) {
// cannot do errcatch(...)
// as recover() works only in first-level called functions
e := _errcatch(recover())
if e == nil {
return
}
e = f(e)
panic(e)
}
// provide error context to automatically add on unwinding.
// f is called if error unwinding is happening.
// call result is added to raised error as "prefix" context
// must be called under defer
func errcontext(f func() interface{}) {
e := _errcatch(recover())
if e == nil {
return
}
arg := f()
panic(erraddcontext(e, arg))
}
// add "prefix" context to error
func erraddcontext(e *Error, arg interface{}) *Error {
return &Error{arg, e}
}
func _myfuncname(nskip int) string {
pcv := [1]uintptr{}
runtime.Callers(nskip, pcv[:])
f := runtime.FuncForPC(pcv[0])
if f == nil {
return ""
}
return f.Name()
}
// get name of currently running function (caller of myfuncname())
// name is fully qualified package/name.function(.x)
func myfuncname() string {
return _myfuncname(3)
}
// get name of currently running function's package
// package is fully qualified package/name
func mypkgname() string {
myfunc := _myfuncname(3)
if myfunc == "" {
return ""
}
// NOTE dots in package name are after last slash are escaped by go as %2e
// this way the first '.' after last '/' is delimiter between package and function
//
// lab.nexedi.com/kirr/git-backup/package%2ename.Function
// lab.nexedi.com/kirr/git-backup/pkg2.qqq/name%2ezzz.Function
islash := strings.LastIndexByte(myfunc, '/')
iafterslash := islash + 1 // NOTE if '/' not found iafterslash = 0
idot := strings.IndexByte(myfunc[iafterslash:], '.')
if idot == -1 {
panic(fmt.Errorf("funcname %q is not fully qualified", myfunc))
}
return myfunc[:iafterslash+idot]
}
// TODO(go1.7) goes away in favour of runtime.Frame
type Frame struct {
*runtime.Func
pc uintptr
}
// get current calling traceback as []Frame
// nskip meaning: the same as in runtime.Callers()
// TODO(go1.7) []Frame -> []runtime.Frame
func xtraceback(nskip int) []Frame {
// all callers
var pcv = []uintptr{0}
for {
pcv = make([]uintptr, 2*len(pcv))
n := runtime.Callers(nskip+1, pcv)
if n < len(pcv) {
pcv = pcv[:n]
break
}
}
// pcv -> frames
/*
framev := make([]runtime.Frame, 0, len(pcv))
frames := runtime.CallersFrames(pcv)
for more := true; more; {
var frame runtime.Frame
frame, more = frames.Next()
framev = append(framev, frame)
}
*/
framev := make([]Frame, 0, len(pcv))
for _, pc := range pcv {
framev = append(framev, Frame{runtime.FuncForPC(pc), pc})
}
return framev
}
var (
_errorpkgname string // package name under which error.go lives
_errorpkgdot string // errorpkg.
_errorraise string // errorpkg.raise
)
func init() {
_errorpkgname = mypkgname()
_errorpkgdot = _errorpkgname + "."
_errorraise = _errorpkgname + ".raise"
}
// add calling context to error.
// Add calling function names as error context up-to topfunc not including.
// see also: erraddcontext()
func erraddcallingcontext(topfunc string, e *Error) *Error {
seenraise := false
for _, f := range xtraceback(2) {
// do not show anything after raise*()
if !seenraise && strings.HasPrefix(f.Name(), _errorraise) {
seenraise = true
continue
}
if !seenraise {
continue
}
// do not go beyond topfunc
if topfunc != "" && f.Name() == topfunc {
break
}
// skip intermediates
if strings.HasSuffix(f.Name(), "_") { // XXX -> better skipfunc
continue
}
e = &Error{f, e}
}
return e
}
// error merging multiple errors (e.g. after collecting them from several parallel workers)
type Errorv []error
func (ev Errorv) Error() string {
if len(ev) == 1 {
return ev[0].Error()
}
msg := fmt.Sprintf("%d errors:\n", len(ev))
for _, e := range ev {
msg += fmt.Sprintf("\t- %s\n", e)
}
return msg
}
// Copyright (C) 2015-2016 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
package main
import (
"errors"
"strings"
"testing"
)
func do_raise1() {
raise(1)
}
func TestErrRaiseCatch(t *testing.T) {
defer errcatch(func(e *Error) {
if !(e.arg == 1 && e.link == nil) {
t.Fatalf("error caught but unexpected: %#v ; want {1, nil}", e)
}
})
do_raise1()
t.Fatal("error not caught")
}
// verify err chain has .arg(s) as expected
func verifyErrChain(t *testing.T, e *Error, argv ...interface{}) {
i := 0
for ; e != nil; i, e = i+1, e.link {
if i >= len(argv) {
t.Fatal("too long error chain")
}
if e.arg != argv[i] {
t.Fatalf("error caught but unexpected %vth arg: %v ; want %v", i, e.arg, argv[i])
}
}
if i < len(argv) {
t.Fatal("too small error chain")
}
}
func do_onunwind1(t *testing.T) {
defer erronunwind(func(e *Error) *Error {
t.Fatal("on unwind called without raise")
return nil
})
}
func do_onunwind2() {
defer erronunwind(func(e *Error) *Error {
return &Error{2, e}
})
do_raise1()
}
func TestErrOnUnwind(t *testing.T) {
defer errcatch(func(e *Error) {
verifyErrChain(t, e, 2, 1)
})
do_onunwind1(t)
do_onunwind2()
t.Fatal("error not caught")
}
func do_context1(t *testing.T) {
defer errcontext(func() interface{} {
t.Fatal("on context called without raise")
return nil
})
}
func do_context2() {
defer errcontext(func() interface{} {
return 3
})
do_raise1()
}
func TestErrContext(t *testing.T) {
defer errcatch(func(e *Error) {
verifyErrChain(t, e, 3, 1)
})
do_context1(t)
do_context2()
t.Fatal("error not caught")
}
func TestMyFuncName(t *testing.T) {
myfunc := myfuncname()
// go test changes full package name (putting filesystem of the tree into ti)
// thus we check only for suffix
wantsuffix := ".TestMyFuncName"
if !strings.HasSuffix(myfunc, wantsuffix) {
t.Errorf("myfuncname() -> %v ; want *%v", myfunc, wantsuffix)
}
}
func do_raise11() {
do_raise1()
}
func do_raise3if() {
raiseif(errors.New("3"))
}
func do_raise3if1() {
do_raise3if()
}
func do_raise4f() {
raisef("%d", 4)
}
func do_raise4f1() {
do_raise4f()
}
func TestErrAddCallingContext(t *testing.T) {
var tests = []struct{ f func(); wanterrcontext string } {
{do_raise11, "do_raise11: do_raise1: 1"},
{do_raise3if1, "do_raise3if1: do_raise3if: 3"},
{do_raise4f1, "do_raise4f1: do_raise4f: 4"},
}
for _, tt := range tests {
func() {
myfunc := myfuncname()
defer errcatch(func(e *Error) {
e = erraddcallingcontext(myfunc, e)
msg := e.Error()
if msg != tt.wanterrcontext {
t.Fatalf("err + calling context: %q ; want %q", msg, tt.wanterrcontext)
}
})
tt.f()
t.Fatal("error not caught")
}()
}
}
...@@ -75,6 +75,10 @@ import ( ...@@ -75,6 +75,10 @@ import (
"syscall" "syscall"
"time" "time"
"lab.nexedi.com/kirr/go123/exc"
"lab.nexedi.com/kirr/go123/myname"
"lab.nexedi.com/kirr/go123/xerr"
git "github.com/libgit2/git2go" git "github.com/libgit2/git2go"
) )
...@@ -122,20 +126,20 @@ func file_to_blob(g *git.Repository, path string) (Sha1, uint32) { ...@@ -122,20 +126,20 @@ func file_to_blob(g *git.Repository, path string) (Sha1, uint32) {
var st syscall.Stat_t var st syscall.Stat_t
err := syscall.Lstat(path, &st) err := syscall.Lstat(path, &st)
if err != nil { if err != nil {
raise(&os.PathError{"lstat", path, err}) exc.Raise(&os.PathError{"lstat", path, err})
} }
if st.Mode&syscall.S_IFMT == syscall.S_IFLNK { if st.Mode&syscall.S_IFMT == syscall.S_IFLNK {
__, err := os.Readlink(path) __, err := os.Readlink(path)
blob_content = Bytes(__) blob_content = Bytes(__)
raiseif(err) exc.Raiseif(err)
} else { } else {
blob_content, err = ioutil.ReadFile(path) blob_content, err = ioutil.ReadFile(path)
raiseif(err) exc.Raiseif(err)
} }
blob_sha1, err := WriteObject(g, blob_content, git.ObjectBlob) blob_sha1, err := WriteObject(g, blob_content, git.ObjectBlob)
raiseif(err) exc.Raiseif(err)
return blob_sha1, st.Mode return blob_sha1, st.Mode
} }
...@@ -143,19 +147,19 @@ func file_to_blob(g *git.Repository, path string) (Sha1, uint32) { ...@@ -143,19 +147,19 @@ func file_to_blob(g *git.Repository, path string) (Sha1, uint32) {
// blob_sha1, mode -> file // blob_sha1, mode -> file
func blob_to_file(g *git.Repository, blob_sha1 Sha1, mode uint32, path string) { func blob_to_file(g *git.Repository, blob_sha1 Sha1, mode uint32, path string) {
blob, err := ReadObject(g, blob_sha1, git.ObjectBlob) blob, err := ReadObject(g, blob_sha1, git.ObjectBlob)
raiseif(err) exc.Raiseif(err)
blob_content := blob.Data() blob_content := blob.Data()
err = os.MkdirAll(pathpkg.Dir(path), 0777) err = os.MkdirAll(pathpkg.Dir(path), 0777)
raiseif(err) exc.Raiseif(err)
if mode&syscall.S_IFMT == syscall.S_IFLNK { if mode&syscall.S_IFMT == syscall.S_IFLNK {
err = os.Symlink(String(blob_content), path) err = os.Symlink(String(blob_content), path)
raiseif(err) exc.Raiseif(err)
} else { } else {
// NOTE mode is native - we cannot use ioutil.WriteFile() directly // NOTE mode is native - we cannot use ioutil.WriteFile() directly
err = writefile(path, blob_content, mode) err = writefile(path, blob_content, mode)
raiseif(err) exc.Raiseif(err)
} }
} }
...@@ -174,7 +178,7 @@ func blob_to_file(g *git.Repository, blob_sha1 Sha1, mode uint32, path string) { ...@@ -174,7 +178,7 @@ func blob_to_file(g *git.Repository, blob_sha1 Sha1, mode uint32, path string) {
var tag_tree_blob = StrSet{"tag": {}, "tree": {}, "blob": {}} var tag_tree_blob = StrSet{"tag": {}, "tree": {}, "blob": {}}
func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1 { func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1 {
if !tag_tree_blob.Contains(obj_type) { if !tag_tree_blob.Contains(obj_type) {
raisef("%s (%s): cannot encode as commit", sha1, obj_type) exc.Raisef("%s (%s): cannot encode as commit", sha1, obj_type)
} }
// first line in commit msg = object type // first line in commit msg = object type
...@@ -236,7 +240,7 @@ func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1 ...@@ -236,7 +240,7 @@ func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1
return zcommit_tree(mktree_empty(), []Sha1{commit1}, obj_encoded) return zcommit_tree(mktree_empty(), []Sha1{commit1}, obj_encoded)
} }
raisef("%s (%q): unknown tagged type", sha1, tagged_type) exc.Raisef("%s (%q): unknown tagged type", sha1, tagged_type)
panic(0) panic(0)
} }
...@@ -246,7 +250,7 @@ func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1 ...@@ -246,7 +250,7 @@ func obj_represent_as_commit(g *git.Repository, sha1 Sha1, obj_type string) Sha1
// - tag: recreated object sha1 // - tag: recreated object sha1
// - tree/blob: null sha1 // - tree/blob: null sha1
func obj_recreate_from_commit(g *git.Repository, commit_sha1 Sha1) Sha1 { func obj_recreate_from_commit(g *git.Repository, commit_sha1 Sha1) Sha1 {
xraise := func(info interface{}) { raise(&RecreateObjError{commit_sha1, info}) } xraise := func(info interface{}) { exc.Raise(&RecreateObjError{commit_sha1, info}) }
xraisef := func(f string, a ...interface{}) { xraise(fmt.Sprintf(f, a...)) } xraisef := func(f string, a ...interface{}) { xraise(fmt.Sprintf(f, a...)) }
commit, err := g.LookupCommit(commit_sha1.AsOid()) commit, err := g.LookupCommit(commit_sha1.AsOid())
...@@ -273,7 +277,7 @@ func obj_recreate_from_commit(g *git.Repository, commit_sha1 Sha1) Sha1 { ...@@ -273,7 +277,7 @@ func obj_recreate_from_commit(g *git.Repository, commit_sha1 Sha1) Sha1 {
// re-create tag object // re-create tag object
tag_sha1, err := WriteObject(g, Bytes(obj_raw), git.ObjectTag) tag_sha1, err := WriteObject(g, Bytes(obj_raw), git.ObjectTag)
raiseif(err) exc.Raiseif(err)
// the original tagged object should be already in repository, because we // the original tagged object should be already in repository, because we
// always attach it to encoding commit one way or another, // always attach it to encoding commit one way or another,
...@@ -376,7 +380,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -376,7 +380,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
// prefix namespace and this way won't leave stale removed things) // prefix namespace and this way won't leave stale removed things)
xgit("rm", "--cached", "-r", "--ignore-unmatch", "--", prefix) xgit("rm", "--cached", "-r", "--ignore-unmatch", "--", prefix)
here := myfuncname() here := myname.Func()
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) (errout error) { err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) (errout error) {
// any error -> stop // any error -> stop
if err != nil { if err != nil {
...@@ -385,8 +389,8 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -385,8 +389,8 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
// propagate exceptions properly via filepath.Walk as errors with calling context // propagate exceptions properly via filepath.Walk as errors with calling context
// (filepath is not our code) // (filepath is not our code)
defer errcatch(func(e *Error) { defer exc.Catch(func(e *exc.Error) {
errout = erraddcallingcontext(here, e) errout = exc.Addcallingcontext(here, e)
}) })
// files -> blobs + queue info for adding blobs to index // files -> blobs + queue info for adding blobs to index
...@@ -429,9 +433,9 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -429,9 +433,9 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
// re-raise / raise error after Walk // re-raise / raise error after Walk
if err != nil { if err != nil {
e := aserror(err) e := exc.Aserror(err)
e = erraddcontext(e, "pulling from "+dir) e = exc.Addcontext(e, "pulling from "+dir)
raise(e) exc.Raise(e)
} }
} }
...@@ -463,7 +467,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -463,7 +467,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
sha1, type_, ref := Sha1{}, "", "" sha1, type_, ref := Sha1{}, "", ""
_, err := fmt.Sscanf(__, "%s %s %s\n", &sha1, &type_, &ref) _, err := fmt.Sscanf(__, "%s %s %s\n", &sha1, &type_, &ref)
if err != nil { if err != nil {
raisef("%s: strange for-each-ref entry %q", backup_refs_work, __) exc.Raisef("%s: strange for-each-ref entry %q", backup_refs_work, __)
} }
backup_refs_list = append(backup_refs_list, Ref{ref, sha1}) backup_refs_list = append(backup_refs_list, Ref{ref, sha1})
backup_refs_entry := fmt.Sprintf("%s %s", sha1, strip_prefix(backup_refs_work, ref)) backup_refs_entry := fmt.Sprintf("%s %s", sha1, strip_prefix(backup_refs_work, ref))
...@@ -518,7 +522,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -518,7 +522,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
xgit("update-ref", "--stdin", RunWith{stdin: backup_refs_delete}) xgit("update-ref", "--stdin", RunWith{stdin: backup_refs_delete})
__ := xgit("for-each-ref", backup_refs_work) __ := xgit("for-each-ref", backup_refs_work)
if __ != "" { if __ != "" {
raisef("Backup refs under %s not deleted properly", backup_refs_work) exc.Raisef("Backup refs under %s not deleted properly", backup_refs_work)
} }
// NOTE `delete` deletes only files, but leaves empty dirs around. // NOTE `delete` deletes only files, but leaves empty dirs around.
...@@ -538,7 +542,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) { ...@@ -538,7 +542,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
// pulled repo right after the pull. // pulled repo right after the pull.
gitdir := xgit("rev-parse", "--git-dir") gitdir := xgit("rev-parse", "--git-dir")
err := os.RemoveAll(gitdir+"/"+backup_refs_work) err := os.RemoveAll(gitdir+"/"+backup_refs_work)
raiseif(err) // NOTE err is nil if path does not exist exc.Raiseif(err) // NOTE err is nil if path does not exist
// if we have working copy - update it // if we have working copy - update it
bare := xgit("rev-parse", "--is-bare-repository") bare := xgit("rev-parse", "--is-bare-repository")
...@@ -610,11 +614,11 @@ func cmd_restore(gb *git.Repository, argv []string) { ...@@ -610,11 +614,11 @@ func cmd_restore(gb *git.Repository, argv []string) {
func reporef_split(reporef string) (repo, ref string) { func reporef_split(reporef string) (repo, ref string) {
dotgit := strings.Index(reporef, ".git/") dotgit := strings.Index(reporef, ".git/")
if dotgit == -1 { if dotgit == -1 {
raisef("E: %s is not a ref for a git repo", reporef) exc.Raisef("E: %s is not a ref for a git repo", reporef)
} }
repo, ref = reporef[:dotgit+4], reporef[dotgit+4+1:] repo, ref = reporef[:dotgit+4], reporef[dotgit+4+1:]
repo, err := path_refunescape(repo) // unescape repo name we originally escaped when making backup repo, err := path_refunescape(repo) // unescape repo name we originally escaped when making backup
raiseif(err) exc.Raiseif(err)
return repo, ref return repo, ref
} }
...@@ -707,7 +711,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -707,7 +711,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
backup_refs := xgit("cat-file", "blob", fmt.Sprintf("%s:backup.refs", HEAD)) backup_refs := xgit("cat-file", "blob", fmt.Sprintf("%s:backup.refs", HEAD))
for _, refentry := range splitlines(backup_refs, "\n") { for _, refentry := range splitlines(backup_refs, "\n") {
// sha1 prefix+refname (sha1_) // sha1 prefix+refname (sha1_)
badentry := func() { raisef("E: invalid backup.refs entry: %q", refentry) } badentry := func() { exc.Raisef("E: invalid backup.refs entry: %q", refentry) }
refentryv := strings.Fields(refentry) refentryv := strings.Fields(refentry)
if !(2 <= len(refentryv) && len(refentryv) <= 3) { if !(2 <= len(refentryv) && len(refentryv) <= 3) {
badentry() badentry()
...@@ -730,7 +734,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -730,7 +734,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
} }
if _, alreadyin := repo.refs[ref]; alreadyin { if _, alreadyin := repo.refs[ref]; alreadyin {
raisef("E: duplicate ref %s in backup.refs", reporef) exc.Raisef("E: duplicate ref %s in backup.refs", reporef)
} }
repo.refs[ref] = BackupRefSha1{sha1, sha1_} repo.refs[ref] = BackupRefSha1{sha1, sha1_}
} }
...@@ -758,9 +762,9 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -758,9 +762,9 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
defer wg.Done() defer wg.Done()
defer close(packxq) defer close(packxq)
// raised err -> errch // raised err -> errch
here := myfuncname() here := myname.Func()
defer errcatch(func(e *Error) { defer exc.Catch(func(e *exc.Error) {
errch <- erraddcallingcontext(here, e) errch <- exc.Addcallingcontext(here, e)
}) })
runloop: runloop:
...@@ -769,7 +773,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -769,7 +773,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
// ensure dir did not exist before restore run // ensure dir did not exist before restore run
err := os.Mkdir(dir, 0777) err := os.Mkdir(dir, 0777)
raiseif(err) exc.Raiseif(err)
// files // files
lstree := xgit("ls-tree", "--full-tree", "-r", "-z", "--", HEAD, prefix, RunWith{raw: true}) lstree := xgit("ls-tree", "--full-tree", "-r", "-z", "--", HEAD, prefix, RunWith{raw: true})
...@@ -781,7 +785,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -781,7 +785,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
// - git-backup repository does not have submodules and the like // - git-backup repository does not have submodules and the like
// -> type should be "blob" only // -> type should be "blob" only
if err != nil || type_ != "blob" { if err != nil || type_ != "blob" {
raisef("%s: invalid/unexpected ls-tree entry %q", HEAD, __) exc.Raisef("%s: invalid/unexpected ls-tree entry %q", HEAD, __)
} }
filename = reprefix(prefix, dir, filename) filename = reprefix(prefix, dir, filename)
...@@ -799,7 +803,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -799,7 +803,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
infof("# repo %s\t-> %s", prefix, filedir) infof("# repo %s\t-> %s", prefix, filedir)
for _, __ := range []string{"refs/heads", "refs/tags", "objects/pack"} { for _, __ := range []string{"refs/heads", "refs/tags", "objects/pack"} {
err := os.MkdirAll(filedir+"/"+__, 0777) err := os.MkdirAll(filedir+"/"+__, 0777)
raiseif(err) exc.Raiseif(err)
} }
repos_seen.Add(filedir) repos_seen.Add(filedir)
} }
...@@ -840,9 +844,9 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -840,9 +844,9 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
go func() { go func() {
defer wg.Done() defer wg.Done()
// raised err -> errch // raised err -> errch
here := myfuncname() here := myname.Func()
defer errcatch(func(e *Error) { defer exc.Catch(func(e *exc.Error) {
errch <- erraddcallingcontext(here, e) errch <- exc.Addcallingcontext(here, e)
}) })
runloop: runloop:
...@@ -881,7 +885,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -881,7 +885,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
} }
repo_ref_list := strings.Join(repo_ref_listv, "\n") repo_ref_list := strings.Join(repo_ref_listv, "\n")
if x_ref_list != repo_ref_list { if x_ref_list != repo_ref_list {
raisef("E: extracted %s refs corrupt", p.repopath) exc.Raisef("E: extracted %s refs corrupt", p.repopath)
} }
// check connectivity in recreated repository. // check connectivity in recreated repository.
...@@ -895,7 +899,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -895,7 +899,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
"rev-list", "--objects", "--stdin", "--quiet", RunWith{stdin: p.refs.Sha1HeadsStr()}) "rev-list", "--objects", "--stdin", "--quiet", RunWith{stdin: p.refs.Sha1HeadsStr()})
if gerr != nil { if gerr != nil {
fmt.Fprintln(os.Stderr, "E: Problem while checking connectivity of extracted repo:") fmt.Fprintln(os.Stderr, "E: Problem while checking connectivity of extracted repo:")
raise(gerr) exc.Raise(gerr)
} }
// XXX disabled because it is slow // XXX disabled because it is slow
...@@ -917,7 +921,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -917,7 +921,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
close(errch) close(errch)
}() }()
ev := Errorv{} ev := xerr.Errorv{}
for e := range errch { for e := range errch {
// tell everything to stop on first error // tell everything to stop on first error
if len(ev) == 0 { if len(ev) == 0 {
...@@ -927,7 +931,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec) ...@@ -927,7 +931,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
} }
if len(ev) != 0 { if len(ev) != 0 {
raise(ev) exc.Raise(ev)
} }
} }
...@@ -974,9 +978,9 @@ func main() { ...@@ -974,9 +978,9 @@ func main() {
} }
// catch Error and report info from it // catch Error and report info from it
here := myfuncname() here := myname.Func()
defer errcatch(func(e *Error) { defer exc.Catch(func(e *exc.Error) {
e = erraddcallingcontext(here, e) e = exc.Addcallingcontext(here, e)
fmt.Fprintln(os.Stderr, e) fmt.Fprintln(os.Stderr, e)
// also show traceback if debug // also show traceback if debug
...@@ -990,7 +994,7 @@ func main() { ...@@ -990,7 +994,7 @@ func main() {
// backup repository // backup repository
gb, err := git.OpenRepository(".") gb, err := git.OpenRepository(".")
raiseif(err) exc.Raiseif(err)
cmd(gb, argv[1:]) cmd(gb, argv[1:])
} }
...@@ -22,6 +22,10 @@ import ( ...@@ -22,6 +22,10 @@ import (
"syscall" "syscall"
"testing" "testing"
"lab.nexedi.com/kirr/go123/exc"
"lab.nexedi.com/kirr/go123/myname"
"lab.nexedi.com/kirr/go123/xruntime"
git "github.com/libgit2/git2go" git "github.com/libgit2/git2go"
) )
...@@ -51,16 +55,16 @@ func XSha1(s string) Sha1 { ...@@ -51,16 +55,16 @@ func XSha1(s string) Sha1 {
// verify end-to-end pull-restore // verify end-to-end pull-restore
func TestPullRestore(t *testing.T) { func TestPullRestore(t *testing.T) {
// if something raises -> don't let testing panic - report it as proper error with context. // if something raises -> don't let testing panic - report it as proper error with context.
here := myfuncname() here := myname.Func()
defer errcatch(func(e *Error) { defer exc.Catch(func(e *exc.Error) {
e = erraddcallingcontext(here, e) e = exc.Addcallingcontext(here, e)
// add file:line for failing code inside testing function - so we have exact context to debug // add file:line for failing code inside testing function - so we have exact context to debug
failedat := "" failedat := ""
for _, f := range xtraceback(1) { for _, f := range xruntime.Traceback(1) {
if f.Name() == here { if f.Name() == here {
// TODO(go1.7) -> f.File, f.Line (f becomes runtime.Frame) // TODO(go1.7) -> f.File, f.Line (f becomes runtime.Frame)
file, line := f.FileLine(f.pc - 1) file, line := f.FileLine(f.Pc - 1)
failedat = fmt.Sprintf("%s:%d", filepath.Base(file), line) failedat = fmt.Sprintf("%s:%d", filepath.Base(file), line)
break break
} }
...@@ -251,7 +255,7 @@ func TestPullRestore(t *testing.T) { ...@@ -251,7 +255,7 @@ func TestPullRestore(t *testing.T) {
// now try to pull corrupt repo - pull should refuse if transferred pack contains bad objects // now try to pull corrupt repo - pull should refuse if transferred pack contains bad objects
my2 := mydir + "/testdata/2" my2 := mydir + "/testdata/2"
func() { func() {
defer errcatch(func(e *Error) { defer exc.Catch(func(e *exc.Error) {
// it ok - pull should raise // it ok - pull should raise
}) })
cmd_pull(gb, []string{my2+":b2"}) cmd_pull(gb, []string{my2+":b2"})
......
...@@ -19,6 +19,8 @@ import ( ...@@ -19,6 +19,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"lab.nexedi.com/kirr/go123/exc"
) )
// how/whether to redirect stdio of spawned process // how/whether to redirect stdio of spawned process
...@@ -164,7 +166,7 @@ func ggit2(argv []string, ctx RunWith) (err *GitError, stdout, stderr string) { ...@@ -164,7 +166,7 @@ func ggit2(argv []string, ctx RunWith) (err *GitError, stdout, stderr string) {
e, stdout, stderr := _git(argv, ctx) e, stdout, stderr := _git(argv, ctx)
eexec, _ := e.(*exec.ExitError) eexec, _ := e.(*exec.ExitError)
if e != nil && eexec == nil { if e != nil && eexec == nil {
raisef("git %s : ", strings.Join(argv, " "), e) exc.Raisef("git %s : ", strings.Join(argv, " "), e)
} }
if eexec != nil { if eexec != nil {
err = &GitError{GitErrContext{argv, ctx.stdin, stdout, stderr}, eexec} err = &GitError{GitErrContext{argv, ctx.stdin, stdout, stderr}, eexec}
...@@ -181,7 +183,7 @@ func xgit(argv ...interface{}) string { ...@@ -181,7 +183,7 @@ func xgit(argv ...interface{}) string {
func xgit2(argv []string, ctx RunWith) string { func xgit2(argv []string, ctx RunWith) string {
gerr, stdout, _ := ggit2(argv, ctx) gerr, stdout, _ := ggit2(argv, ctx)
if gerr != nil { if gerr != nil {
raise(gerr) exc.Raise(gerr)
} }
return stdout return stdout
} }
...@@ -205,11 +207,11 @@ func (e *GitSha1Error) Error() string { ...@@ -205,11 +207,11 @@ func (e *GitSha1Error) Error() string {
func xgit2Sha1(argv []string, ctx RunWith) Sha1 { func xgit2Sha1(argv []string, ctx RunWith) Sha1 {
gerr, stdout, stderr := ggit2(argv, ctx) gerr, stdout, stderr := ggit2(argv, ctx)
if gerr != nil { if gerr != nil {
raise(gerr) exc.Raise(gerr)
} }
sha1, err := Sha1Parse(stdout) sha1, err := Sha1Parse(stdout)
if err != nil { if err != nil {
raise(&GitSha1Error{GitErrContext{argv, ctx.stdin, stdout, stderr}}) exc.Raise(&GitSha1Error{GitErrContext{argv, ctx.stdin, stdout, stderr}})
} }
return sha1 return sha1
} }
...@@ -21,6 +21,8 @@ import ( ...@@ -21,6 +21,8 @@ import (
"sync" "sync"
"time" "time"
"lab.nexedi.com/kirr/go123/exc"
git "github.com/libgit2/git2go" git "github.com/libgit2/git2go"
) )
...@@ -96,11 +98,11 @@ type Tag struct { ...@@ -96,11 +98,11 @@ type Tag struct {
// (libgit2 does not provide such functionality at all) // (libgit2 does not provide such functionality at all)
func xload_tag(g *git.Repository, tag_sha1 Sha1) (tag *Tag, tag_obj *git.OdbObject) { func xload_tag(g *git.Repository, tag_sha1 Sha1) (tag *Tag, tag_obj *git.OdbObject) {
tag_obj, err := ReadObject(g, tag_sha1, git.ObjectTag) tag_obj, err := ReadObject(g, tag_sha1, git.ObjectTag)
raiseif(err) exc.Raiseif(err)
tag, err = tag_parse(String(tag_obj.Data())) tag, err = tag_parse(String(tag_obj.Data()))
if err != nil { if err != nil {
raise(&TagLoadError{tag_sha1, err}) exc.Raise(&TagLoadError{tag_sha1, err})
} }
return tag, tag_obj return tag, tag_obj
} }
...@@ -225,7 +227,7 @@ func xcommit_tree2(g *git.Repository, tree Sha1, parents []Sha1, msg string, aut ...@@ -225,7 +227,7 @@ func xcommit_tree2(g *git.Repository, tree Sha1, parents []Sha1, msg string, aut
commit += fmt.Sprintf("\n%s", msg) commit += fmt.Sprintf("\n%s", msg)
sha1, err := WriteObject(g, Bytes(commit), git.ObjectCommit) sha1, err := WriteObject(g, Bytes(commit), git.ObjectCommit)
raiseif(err) exc.Raiseif(err)
return sha1 return sha1
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment