@ -2,18 +2,24 @@
package dehub
import (
"dehub.dev/src/dehub.git/f s"
"byte s"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"dehub.dev/src/dehub.git/fs"
"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/cache"
"gopkg.in/src-d/go-git.v4/plumbing/format/config"
"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 "
"gopkg.in/src-d/go-git.v4/storage/filesystem "
)
const (
@ -33,18 +39,18 @@ var (
MainRefName = plumbing . NewBranchReferenceName ( Main )
)
type rep oOpts struct {
type open Opts struct {
bare bool
}
// OpenOption is an option which can be passed to the OpenRepo function to
// affect the Repo's behavior.
type OpenOption func ( * rep oOpts)
type OpenOption func ( * open Opts )
// 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 * rep oOpts) {
return func ( o * open Opts ) {
o . bare = bare
}
}
@ -52,7 +58,19 @@ func OpenBare(bare bool) OpenOption {
// Repo is an object which allows accessing and modifying the dehub repo.
type Repo struct {
GitRepo * git . Repository
Storer storage . Storer
// GitDirFS corresponds to the .git directory (or the entire repo directory
// if it's a bare repo)
GitDirFS billy . Filesystem
}
func extractGitDirFS ( storer storage . Storer ) ( billy . Filesystem , error ) {
dotGitFSer , ok := storer . ( interface { Filesystem ( ) billy . Filesystem } )
if ! ok {
return nil , fmt . Errorf ( "git storage object of type %T does not expose its underlying filesystem" ,
storer )
}
return dotGitFSer . Filesystem ( ) , nil
}
// OpenRepo opens the dehub repo in the given directory and returns the object
@ -61,7 +79,7 @@ type Repo struct {
// 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 rep oOpts
var opts open Opts
for _ , opt := range options {
opt ( & opts )
}
@ -73,30 +91,141 @@ func OpenRepo(path string, options ...OpenOption) (*Repo, error) {
}
if r . GitRepo , err = git . PlainOpenWithOptions ( path , openOpts ) ; err != nil {
return nil , fmt . Errorf ( "could not open git repo: %w" , err )
} else if r . GitDirFS , err = extractGitDirFS ( r . GitRepo . Storer ) ; err != nil {
return nil , err
}
return & r , nil
}
type initOpts struct {
bare bool
remote bool
}
// InitOption is an option which can be passed into the Init functions to affect
// their behavior.
type InitOption func ( * initOpts )
// InitBare returns an InitOption which, if true is given, causes the Init
// function to initialize the repo without a worktree.
func InitBare ( bare bool ) InitOption {
return func ( o * initOpts ) {
o . bare = bare
}
}
// InitRemote returns an InitOption which, if true is given, causes the Init
// function to initialize the repo with certain git configuration options set
// which make the repo able to be used as a remote repo.
func InitRemote ( remote bool ) InitOption {
return func ( o * initOpts ) {
o . remote = remote
}
}
// InitRepo will initialize a new repository at the given path. If bare is true
// then the repository will not have a worktree.
func InitRepo ( path string , options ... InitOption ) ( * Repo , error ) {
var opts initOpts
for _ , opt := range options {
opt ( & opts )
}
var repo Repo
var err error
if repo . GitRepo , err = git . PlainInit ( path , opts . bare ) ; err != nil {
return nil , fmt . Errorf ( "initializing git repo: %w" , err )
} else if repo . GitDirFS , err = extractGitDirFS ( repo . GitRepo . Storer ) ; err != nil {
return nil , err
} else if err = repo . init ( opts ) ; err != nil {
return nil , fmt . Errorf ( "initializing repo with dehub defaults: %w" , err )
}
return & repo , nil
}
// InitMemRepo initializes an empty repository which only exists in memory.
func InitMemRepo ( ) * Repo {
r , err := git . Init ( memory . NewStorage ( ) , memfs . New ( ) )
func InitMemRepo ( options ... InitOption ) * Repo {
var opts initOpts
for _ , opt := range options {
opt ( & opts )
}
fs := memfs . New ( )
dotGitFS , err := fs . Chroot ( git . GitDirName )
if err != nil {
panic ( err )
}
storage := filesystem . NewStorage ( dotGitFS , cache . NewObjectLRUDefault ( ) )
repo := & Repo { GitRepo : r }
if err := repo . init ( ) ; err != nil {
var worktree billy . Filesystem
if ! opts . bare {
worktree = fs
}
r , err := git . Init ( storage , worktree )
if err != nil {
panic ( err )
}
repo := & Repo { GitRepo : r , GitDirFS : dotGitFS }
if err := repo . init ( opts ) ; err != nil {
panic ( err )
}
return repo
}
func ( r * Repo ) init ( ) error {
func ( r * Repo ) initRemotePreReceive ( bare bool ) error {
if err := r . GitDirFS . MkdirAll ( "hooks" , 0755 ) ; err != nil {
return fmt . Errorf ( "creating hooks directory: %w" , err )
}
preRcvFlags := os . O_WRONLY | os . O_CREATE | os . O_TRUNC
preRcv , err := r . GitDirFS . OpenFile ( "hooks/pre-receive" , preRcvFlags , 0755 )
if err != nil {
return fmt . Errorf ( "opening hooks/pre-receive file: %w" , err )
}
defer preRcv . Close ( )
var preRcvBody string
if bare {
preRcvBody = "#!/bin/sh\nexec dehub hook -bare -pre-receive\n"
} else {
preRcvBody = "#!/bin/sh\nexec dehub hook -pre-receive\n"
}
if _ , err := io . Copy ( preRcv , bytes . NewBufferString ( preRcvBody ) ) ; err != nil {
return fmt . Errorf ( "writing to hooks/pre-receive: %w" , err )
}
return nil
}
func ( r * Repo ) init ( opts initOpts ) error {
headRef := plumbing . NewSymbolicReference ( plumbing . HEAD , MainRefName )
if err := r . GitRepo . Storer . SetReference ( headRef ) ; err != nil {
return fmt . Errorf ( "setting HEAD reference to %q: %w" , MainRefName , err )
}
if opts . remote {
cfg , err := r . GitRepo . Config ( )
if err != nil {
return fmt . Errorf ( "opening git cfg: %w" , err )
}
cfg . Raw = cfg . Raw . AddOption ( "http" , config . NoSubsection , "receivepack" , "true" )
if err := r . GitRepo . Storer . SetConfig ( cfg ) ; err != nil {
return fmt . Errorf ( "storing modified git config: %w" , err )
}
if err := r . initRemotePreReceive ( opts . bare ) ; err != nil {
return fmt . Errorf ( "initializing pre-receive hook for remote-enabled repo: %w" , err )
}
}
return nil
}