1. 程式人生 > >src-d/gogit 刪除檔案或目錄

src-d/gogit 刪除檔案或目錄

首先 commit 測試樣例:

package git

import (
	"bytes"
	"strings"
	"time"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"golang.org/x/crypto/openpgp/errors"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
	"gopkg.in/src-d/go-git.v4/storage/memory"

	. "gopkg.in/check.v1"
	"gopkg.in/src-d/go-billy.v4/memfs"
	"gopkg.in/src-d/go-billy.v4/util"
)

func (s *WorktreeSuite) TestCommitInvalidOptions(c *C) {
	r, err := Init(memory.NewStorage(), memfs.New())
	c.Assert(err, IsNil)

	w, err := r.Worktree()
	c.Assert(err, IsNil)

	hash, err := w.Commit("", &CommitOptions{})
	c.Assert(err, Equals, ErrMissingAuthor)
	c.Assert(hash.IsZero(), Equals, true)
}

func (s *WorktreeSuite) TestCommitInitial(c *C) {
	expected := plumbing.NewHash("98c4ac7c29c913f7461eae06e024dc18e80d23a4")

	fs := memfs.New()
	storage := memory.NewStorage()

	r, err := Init(storage, fs)
	c.Assert(err, IsNil)

	w, err := r.Worktree()
	c.Assert(err, IsNil)

	util.WriteFile(fs, "foo", []byte("foo"), 0644)

	_, err = w.Add("foo")
	c.Assert(err, IsNil)

	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
	c.Assert(hash, Equals, expected)
	c.Assert(err, IsNil)

	assertStorageStatus(c, r, 1, 1, 1, expected)
}

func (s *WorktreeSuite) TestCommitParent(c *C) {
	expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22")

	fs := memfs.New()
	w := &Worktree{
		r:          s.Repository,
		Filesystem: fs,
	}

	err := w.Checkout(&CheckoutOptions{})
	c.Assert(err, IsNil)

	util.WriteFile(fs, "foo", []byte("foo"), 0644)

	_, err = w.Add("foo")
	c.Assert(err, IsNil)

	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
	c.Assert(hash, Equals, expected)
	c.Assert(err, IsNil)

	assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
}

func (s *WorktreeSuite) TestCommitAll(c *C) {
	expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28")

	fs := memfs.New()
	w := &Worktree{
		r:          s.Repository,
		Filesystem: fs,
	}

	err := w.Checkout(&CheckoutOptions{})
	c.Assert(err, IsNil)

	util.WriteFile(fs, "LICENSE", []byte("foo"), 0644)
	util.WriteFile(fs, "foo", []byte("foo"), 0644)

	hash, err := w.Commit("foo\n", &CommitOptions{
		All:    true,
		Author: defaultSignature(),
	})

	c.Assert(hash, Equals, expected)
	c.Assert(err, IsNil)

	assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
}

func (s *WorktreeSuite) TestRemoveAndCommitAll(c *C) {
	expected := plumbing.NewHash("907cd576c6ced2ecd3dab34a72bf9cf65944b9a9")

	fs := memfs.New()
	w := &Worktree{
		r:          s.Repository,
		Filesystem: fs,
	}

	err := w.Checkout(&CheckoutOptions{})
	c.Assert(err, IsNil)

	util.WriteFile(fs, "foo", []byte("foo"), 0644)
	_, err = w.Add("foo")
	c.Assert(err, IsNil)

	_, errFirst := w.Commit("Add in Repo\n", &CommitOptions{
		Author: defaultSignature(),
	})
	c.Assert(errFirst, IsNil)

	errRemove := fs.Remove("foo")
	c.Assert(errRemove, IsNil)

	hash, errSecond := w.Commit("Remove foo\n", &CommitOptions{
		All:    true,
		Author: defaultSignature(),
	})
	c.Assert(errSecond, IsNil)

	c.Assert(hash, Equals, expected)
	c.Assert(err, IsNil)

	assertStorageStatus(c, s.Repository, 13, 11, 11, expected)
}

func (s *WorktreeSuite) TestCommitSign(c *C) {
	fs := memfs.New()
	storage := memory.NewStorage()

	r, err := Init(storage, fs)
	c.Assert(err, IsNil)

	w, err := r.Worktree()
	c.Assert(err, IsNil)

	util.WriteFile(fs, "foo", []byte("foo"), 0644)

	_, err = w.Add("foo")
	c.Assert(err, IsNil)

	key := commitSignKey(c, true)
	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
	c.Assert(err, IsNil)

	// Verify the commit.
	pks := new(bytes.Buffer)
	pkw, err := armor.Encode(pks, openpgp.PublicKeyType, nil)
	c.Assert(err, IsNil)

	err = key.Serialize(pkw)
	c.Assert(err, IsNil)
	err = pkw.Close()
	c.Assert(err, IsNil)

	expectedCommit, err := r.CommitObject(hash)
	c.Assert(err, IsNil)
	actual, err := expectedCommit.Verify(pks.String())
	c.Assert(err, IsNil)
	c.Assert(actual.PrimaryKey, DeepEquals, key.PrimaryKey)
}

func (s *WorktreeSuite) TestCommitSignBadKey(c *C) {
	fs := memfs.New()
	storage := memory.NewStorage()

	r, err := Init(storage, fs)
	c.Assert(err, IsNil)

	w, err := r.Worktree()
	c.Assert(err, IsNil)

	util.WriteFile(fs, "foo", []byte("foo"), 0644)

	_, err = w.Add("foo")
	c.Assert(err, IsNil)

	key := commitSignKey(c, false)
	_, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
	c.Assert(err, Equals, errors.InvalidArgumentError("signing key is encrypted"))
}

func assertStorageStatus(
	c *C, r *Repository,
	treesCount, blobCount, commitCount int, head plumbing.Hash,
) {
	trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject)
	c.Assert(err, IsNil)
	blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject)
	c.Assert(err, IsNil)
	commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject)
	c.Assert(err, IsNil)

	c.Assert(lenIterEncodedObjects(trees), Equals, treesCount)
	c.Assert(lenIterEncodedObjects(blobs), Equals, blobCount)
	c.Assert(lenIterEncodedObjects(commits), Equals, commitCount)

	ref, err := r.Head()
	c.Assert(err, IsNil)
	c.Assert(ref.Hash(), Equals, head)
}

func lenIterEncodedObjects(iter storer.EncodedObjectIter) int {
	count := 0
	iter.ForEach(func(plumbing.EncodedObject) error {
		count++
		return nil
	})

	return count
}

func defaultSignature() *object.Signature {
	when, _ := time.Parse(object.DateFormat, "Thu May 04 00:03:43 2017 +0200")
	return &object.Signature{
		Name:  "foo",
		Email: "
[email protected]
", When: when, } } func commitSignKey(c *C, decrypt bool) *openpgp.Entity { s := strings.NewReader(armoredKeyRing) es, err := openpgp.ReadArmoredKeyRing(s) c.Assert(err, IsNil) c.Assert(es, HasLen, 1) c.Assert(es[0].Identities, HasLen, 1) _, ok := es[0].Identities["foo bar <
[email protected]
>"] c.Assert(ok, Equals, true) key := es[0] if decrypt { err = key.PrivateKey.Decrypt([]byte(keyPassphrase)) c.Assert(err, IsNil) } return key }

新增檔案以及目錄是呼叫WorkTree中的Add() 方法;首先看下Add 方法在哪個檔案中,找到WorkTree.go中沒有;為了快速找到Add 只能使用LiteIDE (go 開發的ide),可以直接連線到方法所在的檔案worktree_status.go。

然後其中有remove方法:

func (w *Worktree) Add(path string) (plumbing.Hash, error) {
	// TODO(mcuadros): remove plumbing.Hash from signature at v5.
	s, err := w.Status()
	if err != nil {
		return plumbing.ZeroHash, err
	}

	idx, err := w.r.Storer.Index()
	if err != nil {
		return plumbing.ZeroHash, err
	}

	var h plumbing.Hash
	var added bool

	fi, err := w.Filesystem.Lstat(path)
	if err != nil || !fi.IsDir() {
		added, h, err = w.doAddFile(idx, s, path)
	} else {
		added, err = w.doAddDirectory(idx, s, path)
	}

	if err != nil {
		return h, err
	}

	if !added {
		return h, nil
	}

	return h, w.r.Storer.SetIndex(idx)
}

func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string) (added bool, err error) {
	files, err := w.Filesystem.ReadDir(directory)
	if err != nil {
		return false, err
	}

	for _, file := range files {
		name := path.Join(directory, file.Name())

		var a bool
		if file.IsDir() {
			if file.Name() == GitDirName {
				// ignore special git directory
				continue
			}
			a, err = w.doAddDirectory(idx, s, name)
		} else {
			a, _, err = w.doAddFile(idx, s, name)
		}

		if err != nil {
			return
		}

		if !added && a {
			added = true
		}
	}

	return
}

// AddGlob adds all paths, matching pattern, to the index. If pattern matches a
// directory path, all directory contents are added to the index recursively. No
// error is returned if all matching paths are already staged in index.
func (w *Worktree) AddGlob(pattern string) error {
	files, err := util.Glob(w.Filesystem, pattern)
	if err != nil {
		return err
	}

	if len(files) == 0 {
		return ErrGlobNoMatches
	}

	s, err := w.Status()
	if err != nil {
		return err
	}

	idx, err := w.r.Storer.Index()
	if err != nil {
		return err
	}

	var saveIndex bool
	for _, file := range files {
		fi, err := w.Filesystem.Lstat(file)
		if err != nil {
			return err
		}

		var added bool
		if fi.IsDir() {
			added, err = w.doAddDirectory(idx, s, file)
		} else {
			added, _, err = w.doAddFile(idx, s, file)
		}

		if err != nil {
			return err
		}

		if !saveIndex && added {
			saveIndex = true
		}
	}

	if saveIndex {
		return w.r.Storer.SetIndex(idx)
	}

	return nil
}

// doAddFile create a new blob from path and update the index, added is true if
// the file added is different from the index.
func (w *Worktree) doAddFile(idx *index.Index, s Status, path string) (added bool, h plumbing.Hash, err error) {
	if s.File(path).Worktree == Unmodified {
		return false, h, nil
	}

	h, err = w.copyFileToStorage(path)
	if err != nil {
		if os.IsNotExist(err) {
			added = true
			h, err = w.deleteFromIndex(idx, path)
		}

		return
	}

	if err := w.addOrUpdateFileToIndex(idx, path, h); err != nil {
		return false, h, err
	}

	return true, h, err
}

func (w *Worktree) copyFileToStorage(path string) (hash plumbing.Hash, err error) {
	fi, err := w.Filesystem.Lstat(path)
	if err != nil {
		return plumbing.ZeroHash, err
	}

	obj := w.r.Storer.NewEncodedObject()
	obj.SetType(plumbing.BlobObject)
	obj.SetSize(fi.Size())

	writer, err := obj.Writer()
	if err != nil {
		return plumbing.ZeroHash, err
	}

	defer ioutil.CheckClose(writer, &err)

	if fi.Mode()&os.ModeSymlink != 0 {
		err = w.fillEncodedObjectFromSymlink(writer, path, fi)
	} else {
		err = w.fillEncodedObjectFromFile(writer, path, fi)
	}

	if err != nil {
		return plumbing.ZeroHash, err
	}

	return w.r.Storer.SetEncodedObject(obj)
}

func (w *Worktree) fillEncodedObjectFromFile(dst io.Writer, path string, fi os.FileInfo) (err error) {
	src, err := w.Filesystem.Open(path)
	if err != nil {
		return err
	}

	defer ioutil.CheckClose(src, &err)

	if _, err := io.Copy(dst, src); err != nil {
		return err
	}

	return err
}

func (w *Worktree) fillEncodedObjectFromSymlink(dst io.Writer, path string, fi os.FileInfo) error {
	target, err := w.Filesystem.Readlink(path)
	if err != nil {
		return err
	}

	_, err = dst.Write([]byte(target))
	return err
}

func (w *Worktree) addOrUpdateFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
	e, err := idx.Entry(filename)
	if err != nil && err != index.ErrEntryNotFound {
		return err
	}

	if err == index.ErrEntryNotFound {
		return w.doAddFileToIndex(idx, filename, h)
	}

	return w.doUpdateFileToIndex(e, filename, h)
}

func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
	return w.doUpdateFileToIndex(idx.Add(filename), filename, h)
}

func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbing.Hash) error {
	info, err := w.Filesystem.Lstat(filename)
	if err != nil {
		return err
	}

	e.Hash = h
	e.ModifiedAt = info.ModTime()
	e.Mode, err = filemode.NewFromOSFileMode(info.Mode())
	if err != nil {
		return err
	}

	if e.Mode.IsRegular() {
		e.Size = uint32(info.Size())
	}

	fillSystemInfo(e, info.Sys())
	return nil
}

// Remove removes files from the working tree and from the index.
func (w *Worktree) Remove(path string) (plumbing.Hash, error) {
	// TODO(mcuadros): remove plumbing.Hash from signature at v5.
	idx, err := w.r.Storer.Index()
	if err != nil {
		return plumbing.ZeroHash, err
	}

	var h plumbing.Hash

	fi, err := w.Filesystem.Lstat(path)
	if err != nil || !fi.IsDir() {
		h, err = w.doRemoveFile(idx, path)
	} else {
		_, err = w.doRemoveDirectory(idx, path)
	}
	if err != nil {
		return h, err
	}

	return h, w.r.Storer.SetIndex(idx)
}

func (w *Worktree) doRemoveDirectory(idx *index.Index, directory string) (removed bool, err error) {
	files, err := w.Filesystem.ReadDir(directory)
	if err != nil {
		return false, err
	}

	for _, file := range files {
		name := path.Join(directory, file.Name())

		var r bool
		if file.IsDir() {
			r, err = w.doRemoveDirectory(idx, name)
		} else {
			_, err = w.doRemoveFile(idx, name)
			if err == index.ErrEntryNotFound {
				err = nil
			}
		}

		if err != nil {
			return
		}

		if !removed && r {
			removed = true
		}
	}

	err = w.removeEmptyDirectory(directory)
	return
}

func (w *Worktree) removeEmptyDirectory(path string) error {
	files, err := w.Filesystem.ReadDir(path)
	if err != nil {
		return err
	}

	if len(files) != 0 {
		return nil
	}

	return w.Filesystem.Remove(path)
}

func (w *Worktree) doRemoveFile(idx *index.Index, path string) (plumbing.Hash, error) {
	hash, err := w.deleteFromIndex(idx, path)
	if err != nil {
		return plumbing.ZeroHash, err
	}

	return hash, w.deleteFromFilesystem(path)
}

func (w *Worktree) deleteFromIndex(idx *index.Index, path string) (plumbing.Hash, error) {
	e, err := idx.Remove(path)
	if err != nil {
		return plumbing.ZeroHash, err
	}

	return e.Hash, nil
}

func (w *Worktree) deleteFromFilesystem(path string) error {
	err := w.Filesystem.Remove(path)
	if os.IsNotExist(err) {
		return nil
	}

	return err
}

// RemoveGlob removes all paths, matching pattern, from the index. If pattern
// matches a directory path, all directory contents are removed from the index
// recursively.
func (w *Worktree) RemoveGlob(pattern string) error {
	idx, err := w.r.Storer.Index()
	if err != nil {
		return err
	}

	entries, err := idx.Glob(pattern)
	if err != nil {
		return err
	}

	for _, e := range entries {
		file := filepath.FromSlash(e.Name)
		if _, err := w.Filesystem.Lstat(file); err != nil && !os.IsNotExist(err) {
			return err
		}

		if _, err := w.doRemoveFile(idx, file); err != nil {
			return err
		}

		dir, _ := filepath.Split(file)
		if err := w.removeEmptyDirectory(dir); err != nil {
			return err
		}
	}

	return w.r.Storer.SetIndex(idx)
}

呼叫remove 然後commit 即可!!