|
- package git
-
- import (
- "bytes"
- "context"
- "errors"
- "fmt"
-
- "gopkg.in/src-d/go-billy.v4"
- "gopkg.in/src-d/go-git.v4/config"
- "gopkg.in/src-d/go-git.v4/plumbing"
- "gopkg.in/src-d/go-git.v4/plumbing/format/index"
- )
-
- var (
- ErrSubmoduleAlreadyInitialized = errors.New("submodule already initialized")
- ErrSubmoduleNotInitialized = errors.New("submodule not initialized")
- )
-
- // Submodule a submodule allows you to keep another Git repository in a
- // subdirectory of your repository.
- type Submodule struct {
- // initialized defines if a submodule was already initialized.
- initialized bool
-
- c *config.Submodule
- w *Worktree
- }
-
- // Config returns the submodule config
- func (s *Submodule) Config() *config.Submodule {
- return s.c
- }
-
- // Init initialize the submodule reading the recorded Entry in the index for
- // the given submodule
- func (s *Submodule) Init() error {
- cfg, err := s.w.r.Storer.Config()
- if err != nil {
- return err
- }
-
- _, ok := cfg.Submodules[s.c.Name]
- if ok {
- return ErrSubmoduleAlreadyInitialized
- }
-
- s.initialized = true
-
- cfg.Submodules[s.c.Name] = s.c
- return s.w.r.Storer.SetConfig(cfg)
- }
-
- // Status returns the status of the submodule.
- func (s *Submodule) Status() (*SubmoduleStatus, error) {
- idx, err := s.w.r.Storer.Index()
- if err != nil {
- return nil, err
- }
-
- return s.status(idx)
- }
-
- func (s *Submodule) status(idx *index.Index) (*SubmoduleStatus, error) {
- status := &SubmoduleStatus{
- Path: s.c.Path,
- }
-
- e, err := idx.Entry(s.c.Path)
- if err != nil && err != index.ErrEntryNotFound {
- return nil, err
- }
-
- if e != nil {
- status.Expected = e.Hash
- }
-
- if !s.initialized {
- return status, nil
- }
-
- r, err := s.Repository()
- if err != nil {
- return nil, err
- }
-
- head, err := r.Head()
- if err == nil {
- status.Current = head.Hash()
- }
-
- if err != nil && err == plumbing.ErrReferenceNotFound {
- err = nil
- }
-
- return status, err
- }
-
- // Repository returns the Repository represented by this submodule
- func (s *Submodule) Repository() (*Repository, error) {
- if !s.initialized {
- return nil, ErrSubmoduleNotInitialized
- }
-
- storer, err := s.w.r.Storer.Module(s.c.Name)
- if err != nil {
- return nil, err
- }
-
- _, err = storer.Reference(plumbing.HEAD)
- if err != nil && err != plumbing.ErrReferenceNotFound {
- return nil, err
- }
-
- var exists bool
- if err == nil {
- exists = true
- }
-
- var worktree billy.Filesystem
- if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil {
- return nil, err
- }
-
- if exists {
- return Open(storer, worktree)
- }
-
- r, err := Init(storer, worktree)
- if err != nil {
- return nil, err
- }
-
- _, err = r.CreateRemote(&config.RemoteConfig{
- Name: DefaultRemoteName,
- URLs: []string{s.c.URL},
- })
-
- return r, err
- }
-
- // Update the registered submodule to match what the superproject expects, the
- // submodule should be initialized first calling the Init method or setting in
- // the options SubmoduleUpdateOptions.Init equals true
- func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
- return s.UpdateContext(context.Background(), o)
- }
-
- // UpdateContext the registered submodule to match what the superproject
- // expects, the submodule should be initialized first calling the Init method or
- // setting in the options SubmoduleUpdateOptions.Init equals true.
- //
- // The provided Context must be non-nil. If the context expires before the
- // operation is complete, an error is returned. The context only affects to the
- // transport operations.
- func (s *Submodule) UpdateContext(ctx context.Context, o *SubmoduleUpdateOptions) error {
- return s.update(ctx, o, plumbing.ZeroHash)
- }
-
- func (s *Submodule) update(ctx context.Context, o *SubmoduleUpdateOptions, forceHash plumbing.Hash) error {
- if !s.initialized && !o.Init {
- return ErrSubmoduleNotInitialized
- }
-
- if !s.initialized && o.Init {
- if err := s.Init(); err != nil {
- return err
- }
- }
-
- idx, err := s.w.r.Storer.Index()
- if err != nil {
- return err
- }
-
- hash := forceHash
- if hash.IsZero() {
- e, err := idx.Entry(s.c.Path)
- if err != nil {
- return err
- }
-
- hash = e.Hash
- }
-
- r, err := s.Repository()
- if err != nil {
- return err
- }
-
- if err := s.fetchAndCheckout(ctx, r, o, hash); err != nil {
- return err
- }
-
- return s.doRecursiveUpdate(r, o)
- }
-
- func (s *Submodule) doRecursiveUpdate(r *Repository, o *SubmoduleUpdateOptions) error {
- if o.RecurseSubmodules == NoRecurseSubmodules {
- return nil
- }
-
- w, err := r.Worktree()
- if err != nil {
- return err
- }
-
- l, err := w.Submodules()
- if err != nil {
- return err
- }
-
- new := &SubmoduleUpdateOptions{}
- *new = *o
-
- new.RecurseSubmodules--
- return l.Update(new)
- }
-
- func (s *Submodule) fetchAndCheckout(
- ctx context.Context, r *Repository, o *SubmoduleUpdateOptions, hash plumbing.Hash,
- ) error {
- if !o.NoFetch {
- err := r.FetchContext(ctx, &FetchOptions{Auth: o.Auth})
- if err != nil && err != NoErrAlreadyUpToDate {
- return err
- }
- }
-
- w, err := r.Worktree()
- if err != nil {
- return err
- }
-
- if err := w.Checkout(&CheckoutOptions{Hash: hash}); err != nil {
- return err
- }
-
- head := plumbing.NewHashReference(plumbing.HEAD, hash)
- return r.Storer.SetReference(head)
- }
-
- // Submodules list of several submodules from the same repository.
- type Submodules []*Submodule
-
- // Init initializes the submodules in this list.
- func (s Submodules) Init() error {
- for _, sub := range s {
- if err := sub.Init(); err != nil {
- return err
- }
- }
-
- return nil
- }
-
- // Update updates all the submodules in this list.
- func (s Submodules) Update(o *SubmoduleUpdateOptions) error {
- return s.UpdateContext(context.Background(), o)
- }
-
- // UpdateContext updates all the submodules in this list.
- //
- // The provided Context must be non-nil. If the context expires before the
- // operation is complete, an error is returned. The context only affects to the
- // transport operations.
- func (s Submodules) UpdateContext(ctx context.Context, o *SubmoduleUpdateOptions) error {
- for _, sub := range s {
- if err := sub.UpdateContext(ctx, o); err != nil {
- return err
- }
- }
-
- return nil
- }
-
- // Status returns the status of the submodules.
- func (s Submodules) Status() (SubmodulesStatus, error) {
- var list SubmodulesStatus
-
- var r *Repository
- for _, sub := range s {
- if r == nil {
- r = sub.w.r
- }
-
- idx, err := r.Storer.Index()
- if err != nil {
- return nil, err
- }
-
- status, err := sub.status(idx)
- if err != nil {
- return nil, err
- }
-
- list = append(list, status)
- }
-
- return list, nil
- }
-
- // SubmodulesStatus contains the status for all submodiles in the worktree
- type SubmodulesStatus []*SubmoduleStatus
-
- // String is equivalent to `git submodule status`
- func (s SubmodulesStatus) String() string {
- buf := bytes.NewBuffer(nil)
- for _, sub := range s {
- fmt.Fprintln(buf, sub)
- }
-
- return buf.String()
- }
-
- // SubmoduleStatus contains the status for a submodule in the worktree
- type SubmoduleStatus struct {
- Path string
- Current plumbing.Hash
- Expected plumbing.Hash
- Branch plumbing.ReferenceName
- }
-
- // IsClean is the HEAD of the submodule is equals to the expected commit
- func (s *SubmoduleStatus) IsClean() bool {
- return s.Current == s.Expected
- }
-
- // String is equivalent to `git submodule status <submodule>`
- //
- // This will print the SHA-1 of the currently checked out commit for a
- // submodule, along with the submodule path and the output of git describe fo
- // the SHA-1. Each SHA-1 will be prefixed with - if the submodule is not
- // initialized, + if the currently checked out submodule commit does not match
- // the SHA-1 found in the index of the containing repository.
- func (s *SubmoduleStatus) String() string {
- var extra string
- var status = ' '
-
- if s.Current.IsZero() {
- status = '-'
- } else if !s.IsClean() {
- status = '+'
- }
-
- if len(s.Branch) != 0 {
- extra = string(s.Branch[5:])
- } else if !s.Current.IsZero() {
- extra = s.Current.String()[:7]
- }
-
- if extra != "" {
- extra = fmt.Sprintf(" (%s)", extra)
- }
-
- return fmt.Sprintf("%c%s %s%s", status, s.Expected, s.Path, extra)
- }
|