76309b51cb
message: |- Refactor access controls to support multiple branches This was a big lift. It implements a backwards incompatible change to overhaul access control patterns to also encompass which branch is being interacted with, not only which files. The `accessctl` package was significantly rewritten to support this, as well as some of the code modifying it. The INTRODUCTION and SPEC were also appropriately updated. The change to the SPEC is _technically_ backwards incompatible, but it won't effect anything. The `access_control` previously being used will just be ignored, and the changes to `accessctl` include the definition of fallback access controls which will automatically be applied if nothing else matches, so when verifying the older history of this repo those will be used. change_hash: AIfNYLmOLGpuyTiVodT3hDe9lF4E+5DbOTgSdkbjJONb credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5aw0sACgkQlcRvpqQRSKy7kw//UMyS/waV/tE1vntZrMbmEtFmiXPcMVNal76cjhdiF3He50qXoWG6m0qWz+arD1tbjoZml6pvU+Xt45y/Uc54DZizzz0E9azoFW0/uvZiLApftFRArZbT9GhbDs2afalyoTJx/xvQu+a2FD/zfljEWE8Zix+bwHCLojiYHHVA65HFLEt8RsH33jFyzWvS9a2yYqZXL0qrU9tdV68hazdIm1LCp+lyVV74TjwxPAZDOmNAE9l4EjIk1pgr2Qo4u2KwJqCGdVCvka8TiFFYiP7C6319ZhDMyj4m9yZsd1xGtBd9zABVBDgmzCEjt0LI3Tv35lPd2tpFBkjQy0WGcMAhwSHWSP7lxukQMCEB7og/SwtKaExiBJhf1HRO6H9MlhNSW4X1xwUgP+739ixKKUY/RcyXgZ4pkzt6sewAMVbUOcmzXdUvuyDJQ0nhDFowgicpSh1m8tTkN1aLUx18NfnGZRgkgBeE6EpT5/+NBfFwvpiQkXZ3bcMiNhNTU/UnWMyqjKlog+8Ca/7CqgswYppMaw4iPaC54H8P6JTH+XnqDlLKSkvh7PiJJa5nFDG07fqc8lYKm1KGv6virAhEsz/AYKLoNGIsqXt+mYUDLvQpjlRsiN52euxyn5e41LcrH0RidIGMVeaS+7re1pWbkCkMMMtYlnCbC5L6mfrBu6doN8o= account: mediocregopher
176 lines
4.5 KiB
Go
176 lines
4.5 KiB
Go
// Package dehub TODO needs package docs
|
|
package dehub
|
|
|
|
import (
|
|
"dehub/fs"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/src-d/go-billy.v4"
|
|
"gopkg.in/src-d/go-billy.v4/memfs"
|
|
"gopkg.in/src-d/go-git.v4"
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
|
"gopkg.in/src-d/go-git.v4/storage"
|
|
"gopkg.in/src-d/go-git.v4/storage/memory"
|
|
)
|
|
|
|
const (
|
|
// DehubDir defines the name of the directory where all dehub-related files are
|
|
// expected to be found.
|
|
DehubDir = ".dehub"
|
|
)
|
|
|
|
var (
|
|
// ConfigPath defines the expected path to the Repo's configuration file.
|
|
ConfigPath = filepath.Join(DehubDir, "config.yml")
|
|
|
|
// Trunk defines the name of the trunk branch.
|
|
Trunk = "trunk"
|
|
|
|
// TrunkRefName defines the reference name of the trunk branch.
|
|
TrunkRefName = plumbing.NewBranchReferenceName(Trunk)
|
|
)
|
|
|
|
type repoOpts struct {
|
|
bare bool
|
|
}
|
|
|
|
// OpenOption is an option which can be passed to the OpenRepo function to
|
|
// affect the Repo's behavior.
|
|
type OpenOption func(*repoOpts)
|
|
|
|
// OpenBare returns an OpenOption which, if true is given, causes the OpenRepo
|
|
// function to expect to open a bare repo.
|
|
func OpenBare(bare bool) OpenOption {
|
|
return func(o *repoOpts) {
|
|
o.bare = bare
|
|
}
|
|
}
|
|
|
|
// Repo is an object which allows accessing and modifying the dehub repo.
|
|
type Repo struct {
|
|
GitRepo *git.Repository
|
|
Storer storage.Storer
|
|
}
|
|
|
|
// OpenRepo opens the dehub repo in the given directory and returns the object
|
|
// for it.
|
|
//
|
|
// The given path is expected to have a git repo and .dehub folder already
|
|
// initialized.
|
|
func OpenRepo(path string, options ...OpenOption) (*Repo, error) {
|
|
var opts repoOpts
|
|
for _, opt := range options {
|
|
opt(&opts)
|
|
}
|
|
|
|
r := Repo{}
|
|
var err error
|
|
openOpts := &git.PlainOpenOptions{
|
|
DetectDotGit: !opts.bare,
|
|
}
|
|
if r.GitRepo, err = git.PlainOpenWithOptions(path, openOpts); err != nil {
|
|
return nil, fmt.Errorf("could not open git repo: %w", err)
|
|
}
|
|
|
|
return &r, nil
|
|
}
|
|
|
|
// InitMemRepo initializes an empty repository which only exists in memory.
|
|
func InitMemRepo() *Repo {
|
|
r, err := git.Init(memory.NewStorage(), memfs.New())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
repo := &Repo{GitRepo: r}
|
|
if err := repo.init(); err != nil {
|
|
panic(err)
|
|
}
|
|
return repo
|
|
}
|
|
|
|
func (r *Repo) init() error {
|
|
h := plumbing.NewSymbolicReference(plumbing.HEAD, TrunkRefName)
|
|
if err := r.GitRepo.Storer.SetReference(h); err != nil {
|
|
return fmt.Errorf("could not set HEAD to %q: %w", TrunkRefName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Repo) billyFilesystem() (billy.Filesystem, error) {
|
|
w, err := r.GitRepo.Worktree()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not open git worktree: %w", err)
|
|
}
|
|
return w.Filesystem, nil
|
|
}
|
|
|
|
// CheckedOutBranch returns the name of the currently checked out branch.
|
|
func (r *Repo) CheckedOutBranch() (plumbing.ReferenceName, error) {
|
|
// Head() can't be used for this, because it doesn't handle the case of a
|
|
// newly initialized repo very well.
|
|
ogRef, err := r.GitRepo.Storer.Reference(plumbing.HEAD)
|
|
if err != nil {
|
|
return "", fmt.Errorf("couldn't de-reference HEAD (is it a bare repo?): %w", err)
|
|
}
|
|
|
|
ref := ogRef
|
|
for {
|
|
if ref.Type() != plumbing.SymbolicReference {
|
|
break
|
|
}
|
|
|
|
target := ref.Target()
|
|
if target.IsBranch() {
|
|
return target, nil
|
|
}
|
|
|
|
ref, err = r.GitRepo.Storer.Reference(target)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("could not de-reference HEAD to a branch: %w", err)
|
|
}
|
|
|
|
func (r *Repo) head() (*object.Commit, *object.Tree, error) {
|
|
head, err := r.GitRepo.Head()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not get repo HEAD: %w", err)
|
|
}
|
|
|
|
headHash := head.Hash()
|
|
headCommit, err := r.GitRepo.CommitObject(headHash)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not get commit at HEAD (%q): %w", headHash, err)
|
|
}
|
|
|
|
headTree, err := r.GitRepo.TreeObject(headCommit.TreeHash)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not get tree object at HEAD (commit:%q tree:%q): %w",
|
|
headHash, headCommit.TreeHash, err)
|
|
}
|
|
|
|
return headCommit, headTree, nil
|
|
}
|
|
|
|
// headOrRawFS returns an FS based on the HEAD commit, or if there is no HEAD
|
|
// commit (it's an empty repo) an FS based on the raw filesystem.
|
|
func (r *Repo) headOrRawFS() (fs.FS, error) {
|
|
_, headTree, err := r.head()
|
|
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
|
bfs, err := r.billyFilesystem()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get underlying filesystem: %w", err)
|
|
}
|
|
return fs.FromBillyFilesystem(bfs), nil
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("could not get HEAD tree: %w", err)
|
|
}
|
|
return fs.FromTree(headTree), nil
|
|
}
|