1package git
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"regexp"
11	"runtime"
12	"testing"
13	"time"
14
15	fixtures "github.com/go-git/go-git-fixtures/v4"
16	"github.com/go-git/go-git/v5/config"
17	"github.com/go-git/go-git/v5/plumbing"
18	"github.com/go-git/go-git/v5/plumbing/filemode"
19	"github.com/go-git/go-git/v5/plumbing/format/gitignore"
20	"github.com/go-git/go-git/v5/plumbing/format/index"
21	"github.com/go-git/go-git/v5/plumbing/object"
22	"github.com/go-git/go-git/v5/storage/memory"
23
24	"github.com/go-git/go-billy/v5/memfs"
25	"github.com/go-git/go-billy/v5/osfs"
26	"github.com/go-git/go-billy/v5/util"
27	"golang.org/x/text/unicode/norm"
28	. "gopkg.in/check.v1"
29)
30
31type WorktreeSuite struct {
32	BaseSuite
33}
34
35var _ = Suite(&WorktreeSuite{})
36
37func (s *WorktreeSuite) SetUpTest(c *C) {
38	f := fixtures.Basic().One()
39	s.Repository = s.NewRepositoryWithEmptyWorktree(f)
40}
41
42func (s *WorktreeSuite) TestPullCheckout(c *C) {
43	fs := memfs.New()
44	r, _ := Init(memory.NewStorage(), fs)
45	r.CreateRemote(&config.RemoteConfig{
46		Name: DefaultRemoteName,
47		URLs: []string{s.GetBasicLocalRepositoryURL()},
48	})
49
50	w, err := r.Worktree()
51	c.Assert(err, IsNil)
52
53	err = w.Pull(&PullOptions{})
54	c.Assert(err, IsNil)
55
56	fi, err := fs.ReadDir("")
57	c.Assert(err, IsNil)
58	c.Assert(fi, HasLen, 8)
59}
60
61func (s *WorktreeSuite) TestPullFastForward(c *C) {
62	url := c.MkDir()
63	path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
64
65	server, err := PlainClone(url, false, &CloneOptions{
66		URL: path,
67	})
68	c.Assert(err, IsNil)
69
70	r, err := PlainClone(c.MkDir(), false, &CloneOptions{
71		URL: url,
72	})
73	c.Assert(err, IsNil)
74
75	w, err := server.Worktree()
76	c.Assert(err, IsNil)
77	err = ioutil.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755)
78	c.Assert(err, IsNil)
79	hash, err := w.Commit("foo", &CommitOptions{Author: defaultSignature()})
80	c.Assert(err, IsNil)
81
82	w, err = r.Worktree()
83	c.Assert(err, IsNil)
84
85	err = w.Pull(&PullOptions{})
86	c.Assert(err, IsNil)
87
88	head, err := r.Head()
89	c.Assert(err, IsNil)
90	c.Assert(head.Hash(), Equals, hash)
91}
92
93func (s *WorktreeSuite) TestPullNonFastForward(c *C) {
94	url := c.MkDir()
95	path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
96
97	server, err := PlainClone(url, false, &CloneOptions{
98		URL: path,
99	})
100	c.Assert(err, IsNil)
101
102	r, err := PlainClone(c.MkDir(), false, &CloneOptions{
103		URL: url,
104	})
105	c.Assert(err, IsNil)
106
107	w, err := server.Worktree()
108	c.Assert(err, IsNil)
109	err = ioutil.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755)
110	c.Assert(err, IsNil)
111	_, err = w.Commit("foo", &CommitOptions{Author: defaultSignature()})
112	c.Assert(err, IsNil)
113
114	w, err = r.Worktree()
115	c.Assert(err, IsNil)
116	err = ioutil.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755)
117	c.Assert(err, IsNil)
118	_, err = w.Commit("bar", &CommitOptions{Author: defaultSignature()})
119	c.Assert(err, IsNil)
120
121	err = w.Pull(&PullOptions{})
122	c.Assert(err, Equals, ErrNonFastForwardUpdate)
123}
124
125func (s *WorktreeSuite) TestPullUpdateReferencesIfNeeded(c *C) {
126	r, _ := Init(memory.NewStorage(), memfs.New())
127	r.CreateRemote(&config.RemoteConfig{
128		Name: DefaultRemoteName,
129		URLs: []string{s.GetBasicLocalRepositoryURL()},
130	})
131
132	err := r.Fetch(&FetchOptions{})
133	c.Assert(err, IsNil)
134
135	_, err = r.Reference("refs/heads/master", false)
136	c.Assert(err, NotNil)
137
138	w, err := r.Worktree()
139	c.Assert(err, IsNil)
140
141	err = w.Pull(&PullOptions{})
142	c.Assert(err, IsNil)
143
144	head, err := r.Reference(plumbing.HEAD, true)
145	c.Assert(err, IsNil)
146	c.Assert(head.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
147
148	branch, err := r.Reference("refs/heads/master", false)
149	c.Assert(err, IsNil)
150	c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
151
152	err = w.Pull(&PullOptions{})
153	c.Assert(err, Equals, NoErrAlreadyUpToDate)
154}
155
156func (s *WorktreeSuite) TestPullInSingleBranch(c *C) {
157	r, _ := Init(memory.NewStorage(), memfs.New())
158	err := r.clone(context.Background(), &CloneOptions{
159		URL:          s.GetBasicLocalRepositoryURL(),
160		SingleBranch: true,
161	})
162
163	c.Assert(err, IsNil)
164
165	w, err := r.Worktree()
166	c.Assert(err, IsNil)
167
168	err = w.Pull(&PullOptions{})
169	c.Assert(err, Equals, NoErrAlreadyUpToDate)
170
171	branch, err := r.Reference("refs/heads/master", false)
172	c.Assert(err, IsNil)
173	c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
174
175	branch, err = r.Reference("refs/remotes/foo/branch", false)
176	c.Assert(err, NotNil)
177
178	storage := r.Storer.(*memory.Storage)
179	c.Assert(storage.Objects, HasLen, 28)
180}
181
182func (s *WorktreeSuite) TestPullProgress(c *C) {
183	r, _ := Init(memory.NewStorage(), memfs.New())
184
185	r.CreateRemote(&config.RemoteConfig{
186		Name: DefaultRemoteName,
187		URLs: []string{s.GetBasicLocalRepositoryURL()},
188	})
189
190	w, err := r.Worktree()
191	c.Assert(err, IsNil)
192
193	buf := bytes.NewBuffer(nil)
194	err = w.Pull(&PullOptions{
195		Progress: buf,
196	})
197
198	c.Assert(err, IsNil)
199	c.Assert(buf.Len(), Not(Equals), 0)
200}
201
202func (s *WorktreeSuite) TestPullProgressWithRecursion(c *C) {
203	if testing.Short() {
204		c.Skip("skipping test in short mode.")
205	}
206
207	path := fixtures.ByTag("submodule").One().Worktree().Root()
208
209	dir, err := ioutil.TempDir("", "plain-clone-submodule")
210	c.Assert(err, IsNil)
211	defer os.RemoveAll(dir)
212
213	r, _ := PlainInit(dir, false)
214	r.CreateRemote(&config.RemoteConfig{
215		Name: DefaultRemoteName,
216		URLs: []string{path},
217	})
218
219	w, err := r.Worktree()
220	c.Assert(err, IsNil)
221
222	err = w.Pull(&PullOptions{
223		RecurseSubmodules: DefaultSubmoduleRecursionDepth,
224	})
225	c.Assert(err, IsNil)
226
227	cfg, err := r.Config()
228	c.Assert(err, IsNil)
229	c.Assert(cfg.Submodules, HasLen, 2)
230}
231
232func (s *RepositorySuite) TestPullAdd(c *C) {
233	path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
234
235	r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
236		URL: filepath.Join(path, ".git"),
237	})
238
239	c.Assert(err, IsNil)
240
241	storage := r.Storer.(*memory.Storage)
242	c.Assert(storage.Objects, HasLen, 28)
243
244	branch, err := r.Reference("refs/heads/master", false)
245	c.Assert(err, IsNil)
246	c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
247
248	ExecuteOnPath(c, path,
249		"touch foo",
250		"git add foo",
251		"git commit -m foo foo",
252	)
253
254	w, err := r.Worktree()
255	c.Assert(err, IsNil)
256
257	err = w.Pull(&PullOptions{RemoteName: "origin"})
258	c.Assert(err, IsNil)
259
260	// the commit command has introduced a new commit, tree and blob
261	c.Assert(storage.Objects, HasLen, 31)
262
263	branch, err = r.Reference("refs/heads/master", false)
264	c.Assert(err, IsNil)
265	c.Assert(branch.Hash().String(), Not(Equals), "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
266}
267
268func (s *WorktreeSuite) TestPullAlreadyUptodate(c *C) {
269	path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
270
271	r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
272		URL: filepath.Join(path, ".git"),
273	})
274
275	c.Assert(err, IsNil)
276
277	w, err := r.Worktree()
278	c.Assert(err, IsNil)
279	err = ioutil.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755)
280	c.Assert(err, IsNil)
281	_, err = w.Commit("bar", &CommitOptions{Author: defaultSignature()})
282	c.Assert(err, IsNil)
283
284	err = w.Pull(&PullOptions{})
285	c.Assert(err, Equals, NoErrAlreadyUpToDate)
286}
287
288func (s *WorktreeSuite) TestCheckout(c *C) {
289	fs := memfs.New()
290	w := &Worktree{
291		r:          s.Repository,
292		Filesystem: fs,
293	}
294
295	err := w.Checkout(&CheckoutOptions{
296		Force: true,
297	})
298	c.Assert(err, IsNil)
299
300	entries, err := fs.ReadDir("/")
301	c.Assert(err, IsNil)
302
303	c.Assert(entries, HasLen, 8)
304	ch, err := fs.Open("CHANGELOG")
305	c.Assert(err, IsNil)
306
307	content, err := ioutil.ReadAll(ch)
308	c.Assert(err, IsNil)
309	c.Assert(string(content), Equals, "Initial changelog\n")
310
311	idx, err := s.Repository.Storer.Index()
312	c.Assert(err, IsNil)
313	c.Assert(idx.Entries, HasLen, 9)
314}
315
316func (s *WorktreeSuite) TestCheckoutForce(c *C) {
317	w := &Worktree{
318		r:          s.Repository,
319		Filesystem: memfs.New(),
320	}
321
322	err := w.Checkout(&CheckoutOptions{})
323	c.Assert(err, IsNil)
324
325	w.Filesystem = memfs.New()
326
327	err = w.Checkout(&CheckoutOptions{
328		Force: true,
329	})
330	c.Assert(err, IsNil)
331
332	entries, err := w.Filesystem.ReadDir("/")
333	c.Assert(err, IsNil)
334	c.Assert(entries, HasLen, 8)
335}
336
337func (s *WorktreeSuite) TestCheckoutKeep(c *C) {
338	w := &Worktree{
339		r:          s.Repository,
340		Filesystem: memfs.New(),
341	}
342
343	err := w.Checkout(&CheckoutOptions{
344		Force: true,
345	})
346	c.Assert(err, IsNil)
347
348	// Create a new branch and create a new file.
349	err = w.Checkout(&CheckoutOptions{
350		Branch: plumbing.NewBranchReferenceName("new-branch"),
351		Create: true,
352	})
353	c.Assert(err, IsNil)
354
355	w.Filesystem = memfs.New()
356	f, err := w.Filesystem.Create("new-file.txt")
357	c.Assert(err, IsNil)
358	_, err = f.Write([]byte("DUMMY"))
359	c.Assert(err, IsNil)
360	c.Assert(f.Close(), IsNil)
361
362	// Add the file to staging.
363	_, err = w.Add("new-file.txt")
364	c.Assert(err, IsNil)
365
366	// Switch branch to master, and verify that the new file was kept in staging.
367	err = w.Checkout(&CheckoutOptions{
368		Keep: true,
369	})
370	c.Assert(err, IsNil)
371
372	fi, err := w.Filesystem.Stat("new-file.txt")
373	c.Assert(err, IsNil)
374	c.Assert(fi.Size(), Equals, int64(5))
375}
376
377func (s *WorktreeSuite) TestCheckoutSymlink(c *C) {
378	if runtime.GOOS == "windows" {
379		c.Skip("git doesn't support symlinks by default in windows")
380	}
381
382	dir, err := ioutil.TempDir("", "checkout")
383	c.Assert(err, IsNil)
384	defer os.RemoveAll(dir)
385
386	r, err := PlainInit(dir, false)
387	c.Assert(err, IsNil)
388
389	w, err := r.Worktree()
390	c.Assert(err, IsNil)
391
392	w.Filesystem.Symlink("not-exists", "bar")
393	w.Add("bar")
394	w.Commit("foo", &CommitOptions{Author: defaultSignature()})
395
396	r.Storer.SetIndex(&index.Index{Version: 2})
397	w.Filesystem = osfs.New(filepath.Join(dir, "worktree-empty"))
398
399	err = w.Checkout(&CheckoutOptions{})
400	c.Assert(err, IsNil)
401
402	status, err := w.Status()
403	c.Assert(err, IsNil)
404	c.Assert(status.IsClean(), Equals, true)
405
406	target, err := w.Filesystem.Readlink("bar")
407	c.Assert(target, Equals, "not-exists")
408	c.Assert(err, IsNil)
409}
410
411func (s *WorktreeSuite) TestFilenameNormalization(c *C) {
412	if runtime.GOOS == "windows" {
413		c.Skip("windows paths may contain non utf-8 sequences")
414	}
415
416	url := c.MkDir()
417	path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
418
419	server, err := PlainClone(url, false, &CloneOptions{
420		URL: path,
421	})
422	c.Assert(err, IsNil)
423
424	filename := "페"
425
426	w, err := server.Worktree()
427	c.Assert(err, IsNil)
428
429	writeFile := func(path string) {
430		err := util.WriteFile(w.Filesystem, path, []byte("foo"), 0755)
431		c.Assert(err, IsNil)
432	}
433
434	writeFile(filename)
435	origHash, err := w.Add(filename)
436	c.Assert(err, IsNil)
437	_, err = w.Commit("foo", &CommitOptions{Author: defaultSignature()})
438	c.Assert(err, IsNil)
439
440	r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
441		URL: url,
442	})
443	c.Assert(err, IsNil)
444
445	w, err = r.Worktree()
446	c.Assert(err, IsNil)
447
448	status, err := w.Status()
449	c.Assert(err, IsNil)
450	c.Assert(status.IsClean(), Equals, true)
451
452	err = w.Filesystem.Remove(filename)
453	c.Assert(err, IsNil)
454
455	modFilename := norm.NFKD.String(filename)
456	writeFile(modFilename)
457
458	_, err = w.Add(filename)
459	c.Assert(err, IsNil)
460	modHash, err := w.Add(modFilename)
461	c.Assert(err, IsNil)
462	// At this point we've got two files with the same content.
463	// Hence their hashes must be the same.
464	c.Assert(origHash == modHash, Equals, true)
465
466	status, err = w.Status()
467	c.Assert(err, IsNil)
468	// However, their names are different and the work tree is still dirty.
469	c.Assert(status.IsClean(), Equals, false)
470
471	// Revert back the deletion of the first file.
472	writeFile(filename)
473	_, err = w.Add(filename)
474	c.Assert(err, IsNil)
475
476	status, err = w.Status()
477	c.Assert(err, IsNil)
478	// Still dirty - the second file is added.
479	c.Assert(status.IsClean(), Equals, false)
480
481	_, err = w.Remove(modFilename)
482	c.Assert(err, IsNil)
483
484	status, err = w.Status()
485	c.Assert(err, IsNil)
486	c.Assert(status.IsClean(), Equals, true)
487}
488
489func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) {
490	url := "https://github.com/git-fixtures/submodule.git"
491	r := s.NewRepositoryWithEmptyWorktree(fixtures.ByURL(url).One())
492
493	w, err := r.Worktree()
494	c.Assert(err, IsNil)
495
496	err = w.Checkout(&CheckoutOptions{})
497	c.Assert(err, IsNil)
498
499	status, err := w.Status()
500	c.Assert(err, IsNil)
501	c.Assert(status.IsClean(), Equals, true)
502}
503
504func (s *WorktreeSuite) TestCheckoutSubmoduleInitialized(c *C) {
505	url := "https://github.com/git-fixtures/submodule.git"
506	r := s.NewRepository(fixtures.ByURL(url).One())
507
508	w, err := r.Worktree()
509	c.Assert(err, IsNil)
510
511	sub, err := w.Submodules()
512	c.Assert(err, IsNil)
513
514	err = sub.Update(&SubmoduleUpdateOptions{Init: true})
515	c.Assert(err, IsNil)
516
517	status, err := w.Status()
518	c.Assert(err, IsNil)
519	c.Assert(status.IsClean(), Equals, true)
520}
521
522func (s *WorktreeSuite) TestCheckoutIndexMem(c *C) {
523	fs := memfs.New()
524	w := &Worktree{
525		r:          s.Repository,
526		Filesystem: fs,
527	}
528
529	err := w.Checkout(&CheckoutOptions{})
530	c.Assert(err, IsNil)
531
532	idx, err := s.Repository.Storer.Index()
533	c.Assert(err, IsNil)
534	c.Assert(idx.Entries, HasLen, 9)
535	c.Assert(idx.Entries[0].Hash.String(), Equals, "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88")
536	c.Assert(idx.Entries[0].Name, Equals, ".gitignore")
537	c.Assert(idx.Entries[0].Mode, Equals, filemode.Regular)
538	c.Assert(idx.Entries[0].ModifiedAt.IsZero(), Equals, false)
539	c.Assert(idx.Entries[0].Size, Equals, uint32(189))
540
541	// ctime, dev, inode, uid and gid are not supported on memfs fs
542	c.Assert(idx.Entries[0].CreatedAt.IsZero(), Equals, true)
543	c.Assert(idx.Entries[0].Dev, Equals, uint32(0))
544	c.Assert(idx.Entries[0].Inode, Equals, uint32(0))
545	c.Assert(idx.Entries[0].UID, Equals, uint32(0))
546	c.Assert(idx.Entries[0].GID, Equals, uint32(0))
547}
548
549func (s *WorktreeSuite) TestCheckoutIndexOS(c *C) {
550	dir, err := ioutil.TempDir("", "checkout")
551	c.Assert(err, IsNil)
552	defer os.RemoveAll(dir)
553
554	fs := osfs.New(filepath.Join(dir, "worktree"))
555	w := &Worktree{
556		r:          s.Repository,
557		Filesystem: fs,
558	}
559
560	err = w.Checkout(&CheckoutOptions{})
561	c.Assert(err, IsNil)
562
563	idx, err := s.Repository.Storer.Index()
564	c.Assert(err, IsNil)
565	c.Assert(idx.Entries, HasLen, 9)
566	c.Assert(idx.Entries[0].Hash.String(), Equals, "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88")
567	c.Assert(idx.Entries[0].Name, Equals, ".gitignore")
568	c.Assert(idx.Entries[0].Mode, Equals, filemode.Regular)
569	c.Assert(idx.Entries[0].ModifiedAt.IsZero(), Equals, false)
570	c.Assert(idx.Entries[0].Size, Equals, uint32(189))
571
572	c.Assert(idx.Entries[0].CreatedAt.IsZero(), Equals, false)
573	if runtime.GOOS != "windows" {
574		c.Assert(idx.Entries[0].Dev, Not(Equals), uint32(0))
575		c.Assert(idx.Entries[0].Inode, Not(Equals), uint32(0))
576		c.Assert(idx.Entries[0].UID, Not(Equals), uint32(0))
577		c.Assert(idx.Entries[0].GID, Not(Equals), uint32(0))
578	}
579}
580
581func (s *WorktreeSuite) TestCheckoutBranch(c *C) {
582	w := &Worktree{
583		r:          s.Repository,
584		Filesystem: memfs.New(),
585	}
586
587	err := w.Checkout(&CheckoutOptions{
588		Branch: "refs/heads/branch",
589	})
590	c.Assert(err, IsNil)
591
592	head, err := w.r.Head()
593	c.Assert(err, IsNil)
594	c.Assert(head.Name().String(), Equals, "refs/heads/branch")
595
596	status, err := w.Status()
597	c.Assert(err, IsNil)
598	c.Assert(status.IsClean(), Equals, true)
599}
600
601func (s *WorktreeSuite) TestCheckoutCreateWithHash(c *C) {
602	w := &Worktree{
603		r:          s.Repository,
604		Filesystem: memfs.New(),
605	}
606
607	err := w.Checkout(&CheckoutOptions{
608		Create: true,
609		Branch: "refs/heads/foo",
610		Hash:   plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
611	})
612	c.Assert(err, IsNil)
613
614	head, err := w.r.Head()
615	c.Assert(err, IsNil)
616	c.Assert(head.Name().String(), Equals, "refs/heads/foo")
617	c.Assert(head.Hash(), Equals, plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"))
618
619	status, err := w.Status()
620	c.Assert(err, IsNil)
621	c.Assert(status.IsClean(), Equals, true)
622}
623
624func (s *WorktreeSuite) TestCheckoutCreate(c *C) {
625	w := &Worktree{
626		r:          s.Repository,
627		Filesystem: memfs.New(),
628	}
629
630	err := w.Checkout(&CheckoutOptions{
631		Create: true,
632		Branch: "refs/heads/foo",
633	})
634	c.Assert(err, IsNil)
635
636	head, err := w.r.Head()
637	c.Assert(err, IsNil)
638	c.Assert(head.Name().String(), Equals, "refs/heads/foo")
639	c.Assert(head.Hash(), Equals, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
640
641	status, err := w.Status()
642	c.Assert(err, IsNil)
643	c.Assert(status.IsClean(), Equals, true)
644}
645
646func (s *WorktreeSuite) TestCheckoutBranchAndHash(c *C) {
647	w := &Worktree{
648		r:          s.Repository,
649		Filesystem: memfs.New(),
650	}
651
652	err := w.Checkout(&CheckoutOptions{
653		Branch: "refs/heads/foo",
654		Hash:   plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
655	})
656
657	c.Assert(err, Equals, ErrBranchHashExclusive)
658}
659
660func (s *WorktreeSuite) TestCheckoutCreateMissingBranch(c *C) {
661	w := &Worktree{
662		r:          s.Repository,
663		Filesystem: memfs.New(),
664	}
665
666	err := w.Checkout(&CheckoutOptions{
667		Create: true,
668	})
669
670	c.Assert(err, Equals, ErrCreateRequiresBranch)
671}
672
673func (s *WorktreeSuite) TestCheckoutTag(c *C) {
674	f := fixtures.ByTag("tags").One()
675	r := s.NewRepositoryWithEmptyWorktree(f)
676	w, err := r.Worktree()
677	c.Assert(err, IsNil)
678
679	err = w.Checkout(&CheckoutOptions{})
680	c.Assert(err, IsNil)
681	head, err := w.r.Head()
682	c.Assert(err, IsNil)
683	c.Assert(head.Name().String(), Equals, "refs/heads/master")
684
685	status, err := w.Status()
686	c.Assert(err, IsNil)
687	c.Assert(status.IsClean(), Equals, true)
688
689	err = w.Checkout(&CheckoutOptions{Branch: "refs/tags/lightweight-tag"})
690	c.Assert(err, IsNil)
691	head, err = w.r.Head()
692	c.Assert(err, IsNil)
693	c.Assert(head.Name().String(), Equals, "HEAD")
694	c.Assert(head.Hash().String(), Equals, "f7b877701fbf855b44c0a9e86f3fdce2c298b07f")
695
696	err = w.Checkout(&CheckoutOptions{Branch: "refs/tags/commit-tag"})
697	c.Assert(err, IsNil)
698	head, err = w.r.Head()
699	c.Assert(err, IsNil)
700	c.Assert(head.Name().String(), Equals, "HEAD")
701	c.Assert(head.Hash().String(), Equals, "f7b877701fbf855b44c0a9e86f3fdce2c298b07f")
702
703	err = w.Checkout(&CheckoutOptions{Branch: "refs/tags/tree-tag"})
704	c.Assert(err, NotNil)
705	head, err = w.r.Head()
706	c.Assert(err, IsNil)
707	c.Assert(head.Name().String(), Equals, "HEAD")
708}
709
710func (s *WorktreeSuite) TestCheckoutBisect(c *C) {
711	if testing.Short() {
712		c.Skip("skipping test in short mode.")
713	}
714
715	s.testCheckoutBisect(c, "https://github.com/src-d/go-git.git")
716}
717
718func (s *WorktreeSuite) TestCheckoutBisectSubmodules(c *C) {
719	s.testCheckoutBisect(c, "https://github.com/git-fixtures/submodule.git")
720}
721
722// TestCheckoutBisect simulates a git bisect going through the git history and
723// checking every commit over the previous commit
724func (s *WorktreeSuite) testCheckoutBisect(c *C, url string) {
725	f := fixtures.ByURL(url).One()
726	r := s.NewRepositoryWithEmptyWorktree(f)
727
728	w, err := r.Worktree()
729	c.Assert(err, IsNil)
730
731	iter, err := w.r.Log(&LogOptions{})
732	c.Assert(err, IsNil)
733
734	iter.ForEach(func(commit *object.Commit) error {
735		err := w.Checkout(&CheckoutOptions{Hash: commit.Hash})
736		c.Assert(err, IsNil)
737
738		status, err := w.Status()
739		c.Assert(err, IsNil)
740		c.Assert(status.IsClean(), Equals, true)
741
742		return nil
743	})
744}
745
746func (s *WorktreeSuite) TestStatus(c *C) {
747	fs := memfs.New()
748	w := &Worktree{
749		r:          s.Repository,
750		Filesystem: fs,
751	}
752
753	status, err := w.Status()
754	c.Assert(err, IsNil)
755
756	c.Assert(status.IsClean(), Equals, false)
757	c.Assert(status, HasLen, 9)
758}
759
760func (s *WorktreeSuite) TestStatusEmpty(c *C) {
761	fs := memfs.New()
762	storage := memory.NewStorage()
763
764	r, err := Init(storage, fs)
765	c.Assert(err, IsNil)
766
767	w, err := r.Worktree()
768	c.Assert(err, IsNil)
769
770	status, err := w.Status()
771	c.Assert(err, IsNil)
772	c.Assert(status.IsClean(), Equals, true)
773	c.Assert(status, NotNil)
774}
775
776func (s *WorktreeSuite) TestStatusEmptyDirty(c *C) {
777	fs := memfs.New()
778	err := util.WriteFile(fs, "foo", []byte("foo"), 0755)
779	c.Assert(err, IsNil)
780
781	storage := memory.NewStorage()
782
783	r, err := Init(storage, fs)
784	c.Assert(err, IsNil)
785
786	w, err := r.Worktree()
787	c.Assert(err, IsNil)
788
789	status, err := w.Status()
790	c.Assert(err, IsNil)
791	c.Assert(status.IsClean(), Equals, false)
792	c.Assert(status, HasLen, 1)
793}
794
795func (s *WorktreeSuite) TestReset(c *C) {
796	fs := memfs.New()
797	w := &Worktree{
798		r:          s.Repository,
799		Filesystem: fs,
800	}
801
802	commit := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
803
804	err := w.Checkout(&CheckoutOptions{})
805	c.Assert(err, IsNil)
806
807	branch, err := w.r.Reference(plumbing.Master, false)
808	c.Assert(err, IsNil)
809	c.Assert(branch.Hash(), Not(Equals), commit)
810
811	err = w.Reset(&ResetOptions{Mode: MergeReset, Commit: commit})
812	c.Assert(err, IsNil)
813
814	branch, err = w.r.Reference(plumbing.Master, false)
815	c.Assert(err, IsNil)
816	c.Assert(branch.Hash(), Equals, commit)
817
818	status, err := w.Status()
819	c.Assert(err, IsNil)
820	c.Assert(status.IsClean(), Equals, true)
821}
822
823func (s *WorktreeSuite) TestResetWithUntracked(c *C) {
824	fs := memfs.New()
825	w := &Worktree{
826		r:          s.Repository,
827		Filesystem: fs,
828	}
829
830	commit := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
831
832	err := w.Checkout(&CheckoutOptions{})
833	c.Assert(err, IsNil)
834
835	err = util.WriteFile(fs, "foo", nil, 0755)
836	c.Assert(err, IsNil)
837
838	err = w.Reset(&ResetOptions{Mode: MergeReset, Commit: commit})
839	c.Assert(err, IsNil)
840
841	status, err := w.Status()
842	c.Assert(err, IsNil)
843	c.Assert(status.IsClean(), Equals, true)
844}
845
846func (s *WorktreeSuite) TestResetSoft(c *C) {
847	fs := memfs.New()
848	w := &Worktree{
849		r:          s.Repository,
850		Filesystem: fs,
851	}
852
853	commit := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
854
855	err := w.Checkout(&CheckoutOptions{})
856	c.Assert(err, IsNil)
857
858	err = w.Reset(&ResetOptions{Mode: SoftReset, Commit: commit})
859	c.Assert(err, IsNil)
860
861	branch, err := w.r.Reference(plumbing.Master, false)
862	c.Assert(err, IsNil)
863	c.Assert(branch.Hash(), Equals, commit)
864
865	status, err := w.Status()
866	c.Assert(err, IsNil)
867	c.Assert(status.IsClean(), Equals, false)
868	c.Assert(status.File("CHANGELOG").Staging, Equals, Added)
869}
870
871func (s *WorktreeSuite) TestResetMixed(c *C) {
872	fs := memfs.New()
873	w := &Worktree{
874		r:          s.Repository,
875		Filesystem: fs,
876	}
877
878	commit := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
879
880	err := w.Checkout(&CheckoutOptions{})
881	c.Assert(err, IsNil)
882
883	err = w.Reset(&ResetOptions{Mode: MixedReset, Commit: commit})
884	c.Assert(err, IsNil)
885
886	branch, err := w.r.Reference(plumbing.Master, false)
887	c.Assert(err, IsNil)
888	c.Assert(branch.Hash(), Equals, commit)
889
890	status, err := w.Status()
891	c.Assert(err, IsNil)
892	c.Assert(status.IsClean(), Equals, false)
893	c.Assert(status.File("CHANGELOG").Staging, Equals, Untracked)
894}
895
896func (s *WorktreeSuite) TestResetMerge(c *C) {
897	fs := memfs.New()
898	w := &Worktree{
899		r:          s.Repository,
900		Filesystem: fs,
901	}
902
903	commitA := plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294")
904	commitB := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
905
906	err := w.Checkout(&CheckoutOptions{})
907	c.Assert(err, IsNil)
908
909	err = w.Reset(&ResetOptions{Mode: MergeReset, Commit: commitA})
910	c.Assert(err, IsNil)
911
912	branch, err := w.r.Reference(plumbing.Master, false)
913	c.Assert(err, IsNil)
914	c.Assert(branch.Hash(), Equals, commitA)
915
916	f, err := fs.Create(".gitignore")
917	c.Assert(err, IsNil)
918	_, err = f.Write([]byte("foo"))
919	c.Assert(err, IsNil)
920	err = f.Close()
921	c.Assert(err, IsNil)
922
923	err = w.Reset(&ResetOptions{Mode: MergeReset, Commit: commitB})
924	c.Assert(err, Equals, ErrUnstagedChanges)
925
926	branch, err = w.r.Reference(plumbing.Master, false)
927	c.Assert(err, IsNil)
928	c.Assert(branch.Hash(), Equals, commitA)
929}
930
931func (s *WorktreeSuite) TestResetHard(c *C) {
932	fs := memfs.New()
933	w := &Worktree{
934		r:          s.Repository,
935		Filesystem: fs,
936	}
937
938	commit := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
939
940	err := w.Checkout(&CheckoutOptions{})
941	c.Assert(err, IsNil)
942
943	f, err := fs.Create(".gitignore")
944	c.Assert(err, IsNil)
945	_, err = f.Write([]byte("foo"))
946	c.Assert(err, IsNil)
947	err = f.Close()
948	c.Assert(err, IsNil)
949
950	err = w.Reset(&ResetOptions{Mode: HardReset, Commit: commit})
951	c.Assert(err, IsNil)
952
953	branch, err := w.r.Reference(plumbing.Master, false)
954	c.Assert(err, IsNil)
955	c.Assert(branch.Hash(), Equals, commit)
956}
957
958func (s *WorktreeSuite) TestStatusAfterCheckout(c *C) {
959	fs := memfs.New()
960	w := &Worktree{
961		r:          s.Repository,
962		Filesystem: fs,
963	}
964
965	err := w.Checkout(&CheckoutOptions{Force: true})
966	c.Assert(err, IsNil)
967
968	status, err := w.Status()
969	c.Assert(err, IsNil)
970	c.Assert(status.IsClean(), Equals, true)
971
972}
973
974func (s *WorktreeSuite) TestStatusModified(c *C) {
975	dir, err := ioutil.TempDir("", "status")
976	c.Assert(err, IsNil)
977	defer os.RemoveAll(dir)
978
979	fs := osfs.New(filepath.Join(dir, "worktree"))
980	w := &Worktree{
981		r:          s.Repository,
982		Filesystem: fs,
983	}
984
985	err = w.Checkout(&CheckoutOptions{})
986	c.Assert(err, IsNil)
987
988	f, err := fs.Create(".gitignore")
989	c.Assert(err, IsNil)
990	_, err = f.Write([]byte("foo"))
991	c.Assert(err, IsNil)
992	err = f.Close()
993	c.Assert(err, IsNil)
994
995	status, err := w.Status()
996	c.Assert(err, IsNil)
997	c.Assert(status.IsClean(), Equals, false)
998	c.Assert(status.File(".gitignore").Worktree, Equals, Modified)
999}
1000
1001func (s *WorktreeSuite) TestStatusIgnored(c *C) {
1002	fs := memfs.New()
1003	w := &Worktree{
1004		r:          s.Repository,
1005		Filesystem: fs,
1006	}
1007
1008	w.Checkout(&CheckoutOptions{})
1009
1010	fs.MkdirAll("another", os.ModePerm)
1011	f, _ := fs.Create("another/file")
1012	f.Close()
1013	fs.MkdirAll("vendor/github.com", os.ModePerm)
1014	f, _ = fs.Create("vendor/github.com/file")
1015	f.Close()
1016	fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
1017	f, _ = fs.Create("vendor/gopkg.in/file")
1018	f.Close()
1019
1020	status, _ := w.Status()
1021	c.Assert(len(status), Equals, 3)
1022	_, ok := status["another/file"]
1023	c.Assert(ok, Equals, true)
1024	_, ok = status["vendor/github.com/file"]
1025	c.Assert(ok, Equals, true)
1026	_, ok = status["vendor/gopkg.in/file"]
1027	c.Assert(ok, Equals, true)
1028
1029	f, _ = fs.Create(".gitignore")
1030	f.Write([]byte("vendor/g*/"))
1031	f.Close()
1032	f, _ = fs.Create("vendor/.gitignore")
1033	f.Write([]byte("!github.com/\n"))
1034	f.Close()
1035
1036	status, _ = w.Status()
1037	c.Assert(len(status), Equals, 4)
1038	_, ok = status[".gitignore"]
1039	c.Assert(ok, Equals, true)
1040	_, ok = status["another/file"]
1041	c.Assert(ok, Equals, true)
1042	_, ok = status["vendor/.gitignore"]
1043	c.Assert(ok, Equals, true)
1044	_, ok = status["vendor/github.com/file"]
1045	c.Assert(ok, Equals, true)
1046}
1047
1048func (s *WorktreeSuite) TestStatusUntracked(c *C) {
1049	fs := memfs.New()
1050	w := &Worktree{
1051		r:          s.Repository,
1052		Filesystem: fs,
1053	}
1054
1055	err := w.Checkout(&CheckoutOptions{Force: true})
1056	c.Assert(err, IsNil)
1057
1058	f, err := w.Filesystem.Create("foo")
1059	c.Assert(err, IsNil)
1060	c.Assert(f.Close(), IsNil)
1061
1062	status, err := w.Status()
1063	c.Assert(err, IsNil)
1064	c.Assert(status.File("foo").Staging, Equals, Untracked)
1065	c.Assert(status.File("foo").Worktree, Equals, Untracked)
1066}
1067
1068func (s *WorktreeSuite) TestStatusDeleted(c *C) {
1069	dir, err := ioutil.TempDir("", "status")
1070	c.Assert(err, IsNil)
1071	defer os.RemoveAll(dir)
1072
1073	fs := osfs.New(filepath.Join(dir, "worktree"))
1074	w := &Worktree{
1075		r:          s.Repository,
1076		Filesystem: fs,
1077	}
1078
1079	err = w.Checkout(&CheckoutOptions{})
1080	c.Assert(err, IsNil)
1081
1082	err = fs.Remove(".gitignore")
1083	c.Assert(err, IsNil)
1084
1085	status, err := w.Status()
1086	c.Assert(err, IsNil)
1087	c.Assert(status.IsClean(), Equals, false)
1088	c.Assert(status.File(".gitignore").Worktree, Equals, Deleted)
1089}
1090
1091func (s *WorktreeSuite) TestSubmodule(c *C) {
1092	path := fixtures.ByTag("submodule").One().Worktree().Root()
1093	r, err := PlainOpen(path)
1094	c.Assert(err, IsNil)
1095
1096	w, err := r.Worktree()
1097	c.Assert(err, IsNil)
1098
1099	m, err := w.Submodule("basic")
1100	c.Assert(err, IsNil)
1101
1102	c.Assert(m.Config().Name, Equals, "basic")
1103}
1104
1105func (s *WorktreeSuite) TestSubmodules(c *C) {
1106	path := fixtures.ByTag("submodule").One().Worktree().Root()
1107	r, err := PlainOpen(path)
1108	c.Assert(err, IsNil)
1109
1110	w, err := r.Worktree()
1111	c.Assert(err, IsNil)
1112
1113	l, err := w.Submodules()
1114	c.Assert(err, IsNil)
1115
1116	c.Assert(l, HasLen, 2)
1117}
1118
1119func (s *WorktreeSuite) TestAddUntracked(c *C) {
1120	fs := memfs.New()
1121	w := &Worktree{
1122		r:          s.Repository,
1123		Filesystem: fs,
1124	}
1125
1126	err := w.Checkout(&CheckoutOptions{Force: true})
1127	c.Assert(err, IsNil)
1128
1129	idx, err := w.r.Storer.Index()
1130	c.Assert(err, IsNil)
1131	c.Assert(idx.Entries, HasLen, 9)
1132
1133	err = util.WriteFile(w.Filesystem, "foo", []byte("FOO"), 0755)
1134	c.Assert(err, IsNil)
1135
1136	hash, err := w.Add("foo")
1137	c.Assert(hash.String(), Equals, "d96c7efbfec2814ae0301ad054dc8d9fc416c9b5")
1138	c.Assert(err, IsNil)
1139
1140	idx, err = w.r.Storer.Index()
1141	c.Assert(err, IsNil)
1142	c.Assert(idx.Entries, HasLen, 10)
1143
1144	e, err := idx.Entry("foo")
1145	c.Assert(err, IsNil)
1146	c.Assert(e.Hash, Equals, hash)
1147	c.Assert(e.Mode, Equals, filemode.Executable)
1148
1149	status, err := w.Status()
1150	c.Assert(err, IsNil)
1151	c.Assert(status, HasLen, 1)
1152
1153	file := status.File("foo")
1154	c.Assert(file.Staging, Equals, Added)
1155	c.Assert(file.Worktree, Equals, Unmodified)
1156
1157	obj, err := w.r.Storer.EncodedObject(plumbing.BlobObject, hash)
1158	c.Assert(err, IsNil)
1159	c.Assert(obj, NotNil)
1160	c.Assert(obj.Size(), Equals, int64(3))
1161}
1162
1163func (s *WorktreeSuite) TestIgnored(c *C) {
1164	fs := memfs.New()
1165	w := &Worktree{
1166		r:          s.Repository,
1167		Filesystem: fs,
1168	}
1169
1170	w.Excludes = make([]gitignore.Pattern, 0)
1171	w.Excludes = append(w.Excludes, gitignore.ParsePattern("foo", nil))
1172
1173	err := w.Checkout(&CheckoutOptions{Force: true})
1174	c.Assert(err, IsNil)
1175
1176	idx, err := w.r.Storer.Index()
1177	c.Assert(err, IsNil)
1178	c.Assert(idx.Entries, HasLen, 9)
1179
1180	err = util.WriteFile(w.Filesystem, "foo", []byte("FOO"), 0755)
1181	c.Assert(err, IsNil)
1182
1183	status, err := w.Status()
1184	c.Assert(err, IsNil)
1185	c.Assert(status, HasLen, 0)
1186
1187	file := status.File("foo")
1188	c.Assert(file.Staging, Equals, Untracked)
1189	c.Assert(file.Worktree, Equals, Untracked)
1190}
1191
1192func (s *WorktreeSuite) TestExcludedNoGitignore(c *C) {
1193	f := fixtures.ByTag("empty").One()
1194	r := s.NewRepository(f)
1195
1196	fs := memfs.New()
1197	w := &Worktree{
1198		r:          r,
1199		Filesystem: fs,
1200	}
1201
1202	_, err := fs.Open(".gitignore")
1203	c.Assert(err, Equals, os.ErrNotExist)
1204
1205	w.Excludes = make([]gitignore.Pattern, 0)
1206	w.Excludes = append(w.Excludes, gitignore.ParsePattern("foo", nil))
1207
1208	err = util.WriteFile(w.Filesystem, "foo", []byte("FOO"), 0755)
1209	c.Assert(err, IsNil)
1210
1211	status, err := w.Status()
1212	c.Assert(err, IsNil)
1213	c.Assert(status, HasLen, 0)
1214
1215	file := status.File("foo")
1216	c.Assert(file.Staging, Equals, Untracked)
1217	c.Assert(file.Worktree, Equals, Untracked)
1218}
1219
1220func (s *WorktreeSuite) TestAddModified(c *C) {
1221	fs := memfs.New()
1222	w := &Worktree{
1223		r:          s.Repository,
1224		Filesystem: fs,
1225	}
1226
1227	err := w.Checkout(&CheckoutOptions{Force: true})
1228	c.Assert(err, IsNil)
1229
1230	idx, err := w.r.Storer.Index()
1231	c.Assert(err, IsNil)
1232	c.Assert(idx.Entries, HasLen, 9)
1233
1234	err = util.WriteFile(w.Filesystem, "LICENSE", []byte("FOO"), 0644)
1235	c.Assert(err, IsNil)
1236
1237	hash, err := w.Add("LICENSE")
1238	c.Assert(err, IsNil)
1239	c.Assert(hash.String(), Equals, "d96c7efbfec2814ae0301ad054dc8d9fc416c9b5")
1240
1241	idx, err = w.r.Storer.Index()
1242	c.Assert(err, IsNil)
1243	c.Assert(idx.Entries, HasLen, 9)
1244
1245	e, err := idx.Entry("LICENSE")
1246	c.Assert(err, IsNil)
1247	c.Assert(e.Hash, Equals, hash)
1248	c.Assert(e.Mode, Equals, filemode.Regular)
1249
1250	status, err := w.Status()
1251	c.Assert(err, IsNil)
1252	c.Assert(status, HasLen, 1)
1253
1254	file := status.File("LICENSE")
1255	c.Assert(file.Staging, Equals, Modified)
1256	c.Assert(file.Worktree, Equals, Unmodified)
1257}
1258
1259func (s *WorktreeSuite) TestAddUnmodified(c *C) {
1260	fs := memfs.New()
1261	w := &Worktree{
1262		r:          s.Repository,
1263		Filesystem: fs,
1264	}
1265
1266	err := w.Checkout(&CheckoutOptions{Force: true})
1267	c.Assert(err, IsNil)
1268
1269	hash, err := w.Add("LICENSE")
1270	c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f")
1271	c.Assert(err, IsNil)
1272}
1273
1274func (s *WorktreeSuite) TestAddRemoved(c *C) {
1275	fs := memfs.New()
1276	w := &Worktree{
1277		r:          s.Repository,
1278		Filesystem: fs,
1279	}
1280
1281	err := w.Checkout(&CheckoutOptions{Force: true})
1282	c.Assert(err, IsNil)
1283
1284	idx, err := w.r.Storer.Index()
1285	c.Assert(err, IsNil)
1286	c.Assert(idx.Entries, HasLen, 9)
1287
1288	err = w.Filesystem.Remove("LICENSE")
1289	c.Assert(err, IsNil)
1290
1291	hash, err := w.Add("LICENSE")
1292	c.Assert(err, IsNil)
1293	c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f")
1294
1295	e, err := idx.Entry("LICENSE")
1296	c.Assert(err, IsNil)
1297	c.Assert(e.Hash, Equals, hash)
1298	c.Assert(e.Mode, Equals, filemode.Regular)
1299
1300	status, err := w.Status()
1301	c.Assert(err, IsNil)
1302	c.Assert(status, HasLen, 1)
1303
1304	file := status.File("LICENSE")
1305	c.Assert(file.Staging, Equals, Deleted)
1306}
1307
1308func (s *WorktreeSuite) TestAddSymlink(c *C) {
1309	dir, err := ioutil.TempDir("", "checkout")
1310	c.Assert(err, IsNil)
1311	defer os.RemoveAll(dir)
1312
1313	r, err := PlainInit(dir, false)
1314	c.Assert(err, IsNil)
1315	err = util.WriteFile(r.wt, "foo", []byte("qux"), 0644)
1316	c.Assert(err, IsNil)
1317	err = r.wt.Symlink("foo", "bar")
1318	c.Assert(err, IsNil)
1319
1320	w, err := r.Worktree()
1321	c.Assert(err, IsNil)
1322	h, err := w.Add("foo")
1323	c.Assert(err, IsNil)
1324	c.Assert(h, Not(Equals), plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c"))
1325
1326	h, err = w.Add("bar")
1327	c.Assert(err, IsNil)
1328	c.Assert(h, Equals, plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c"))
1329
1330	obj, err := w.r.Storer.EncodedObject(plumbing.BlobObject, h)
1331	c.Assert(err, IsNil)
1332	c.Assert(obj, NotNil)
1333	c.Assert(obj.Size(), Equals, int64(3))
1334}
1335
1336func (s *WorktreeSuite) TestAddDirectory(c *C) {
1337	fs := memfs.New()
1338	w := &Worktree{
1339		r:          s.Repository,
1340		Filesystem: fs,
1341	}
1342
1343	err := w.Checkout(&CheckoutOptions{Force: true})
1344	c.Assert(err, IsNil)
1345
1346	idx, err := w.r.Storer.Index()
1347	c.Assert(err, IsNil)
1348	c.Assert(idx.Entries, HasLen, 9)
1349
1350	err = util.WriteFile(w.Filesystem, "qux/foo", []byte("FOO"), 0755)
1351	c.Assert(err, IsNil)
1352	err = util.WriteFile(w.Filesystem, "qux/baz/bar", []byte("BAR"), 0755)
1353	c.Assert(err, IsNil)
1354
1355	h, err := w.Add("qux")
1356	c.Assert(err, IsNil)
1357	c.Assert(h.IsZero(), Equals, true)
1358
1359	idx, err = w.r.Storer.Index()
1360	c.Assert(err, IsNil)
1361	c.Assert(idx.Entries, HasLen, 11)
1362
1363	e, err := idx.Entry("qux/foo")
1364	c.Assert(err, IsNil)
1365	c.Assert(e.Mode, Equals, filemode.Executable)
1366
1367	e, err = idx.Entry("qux/baz/bar")
1368	c.Assert(err, IsNil)
1369	c.Assert(e.Mode, Equals, filemode.Executable)
1370
1371	status, err := w.Status()
1372	c.Assert(err, IsNil)
1373	c.Assert(status, HasLen, 2)
1374
1375	file := status.File("qux/foo")
1376	c.Assert(file.Staging, Equals, Added)
1377	c.Assert(file.Worktree, Equals, Unmodified)
1378
1379	file = status.File("qux/baz/bar")
1380	c.Assert(file.Staging, Equals, Added)
1381	c.Assert(file.Worktree, Equals, Unmodified)
1382}
1383
1384func (s *WorktreeSuite) TestAddDirectoryErrorNotFound(c *C) {
1385	r, _ := Init(memory.NewStorage(), memfs.New())
1386	w, _ := r.Worktree()
1387
1388	h, err := w.Add("foo")
1389	c.Assert(err, NotNil)
1390	c.Assert(h.IsZero(), Equals, true)
1391}
1392
1393func (s *WorktreeSuite) TestAddAll(c *C) {
1394	fs := memfs.New()
1395	w := &Worktree{
1396		r:          s.Repository,
1397		Filesystem: fs,
1398	}
1399
1400	err := w.Checkout(&CheckoutOptions{Force: true})
1401	c.Assert(err, IsNil)
1402
1403	idx, err := w.r.Storer.Index()
1404	c.Assert(err, IsNil)
1405	c.Assert(idx.Entries, HasLen, 9)
1406
1407	err = util.WriteFile(w.Filesystem, "file1", []byte("file1"), 0644)
1408	c.Assert(err, IsNil)
1409
1410	err = util.WriteFile(w.Filesystem, "file2", []byte("file2"), 0644)
1411	c.Assert(err, IsNil)
1412
1413	err = util.WriteFile(w.Filesystem, "file3", []byte("ignore me"), 0644)
1414	c.Assert(err, IsNil)
1415
1416	w.Excludes = make([]gitignore.Pattern, 0)
1417	w.Excludes = append(w.Excludes, gitignore.ParsePattern("file3", nil))
1418
1419	err = w.AddWithOptions(&AddOptions{All: true})
1420	c.Assert(err, IsNil)
1421
1422	idx, err = w.r.Storer.Index()
1423	c.Assert(err, IsNil)
1424	c.Assert(idx.Entries, HasLen, 11)
1425
1426	status, err := w.Status()
1427	c.Assert(err, IsNil)
1428	c.Assert(status, HasLen, 2)
1429
1430	file1 := status.File("file1")
1431	c.Assert(file1.Staging, Equals, Added)
1432	file2 := status.File("file2")
1433	c.Assert(file2.Staging, Equals, Added)
1434	file3 := status.File("file3")
1435	c.Assert(file3.Staging, Equals, Untracked)
1436	c.Assert(file3.Worktree, Equals, Untracked)
1437}
1438
1439func (s *WorktreeSuite) TestAddGlob(c *C) {
1440	fs := memfs.New()
1441	w := &Worktree{
1442		r:          s.Repository,
1443		Filesystem: fs,
1444	}
1445
1446	err := w.Checkout(&CheckoutOptions{Force: true})
1447	c.Assert(err, IsNil)
1448
1449	idx, err := w.r.Storer.Index()
1450	c.Assert(err, IsNil)
1451	c.Assert(idx.Entries, HasLen, 9)
1452
1453	err = util.WriteFile(w.Filesystem, "qux/qux", []byte("QUX"), 0755)
1454	c.Assert(err, IsNil)
1455	err = util.WriteFile(w.Filesystem, "qux/baz", []byte("BAZ"), 0755)
1456	c.Assert(err, IsNil)
1457	err = util.WriteFile(w.Filesystem, "qux/bar/baz", []byte("BAZ"), 0755)
1458	c.Assert(err, IsNil)
1459
1460	err = w.AddWithOptions(&AddOptions{Glob: w.Filesystem.Join("qux", "b*")})
1461	c.Assert(err, IsNil)
1462
1463	idx, err = w.r.Storer.Index()
1464	c.Assert(err, IsNil)
1465	c.Assert(idx.Entries, HasLen, 11)
1466
1467	e, err := idx.Entry("qux/baz")
1468	c.Assert(err, IsNil)
1469	c.Assert(e.Mode, Equals, filemode.Executable)
1470
1471	e, err = idx.Entry("qux/bar/baz")
1472	c.Assert(err, IsNil)
1473	c.Assert(e.Mode, Equals, filemode.Executable)
1474
1475	status, err := w.Status()
1476	c.Assert(err, IsNil)
1477	c.Assert(status, HasLen, 3)
1478
1479	file := status.File("qux/qux")
1480	c.Assert(file.Staging, Equals, Untracked)
1481	c.Assert(file.Worktree, Equals, Untracked)
1482
1483	file = status.File("qux/baz")
1484	c.Assert(file.Staging, Equals, Added)
1485	c.Assert(file.Worktree, Equals, Unmodified)
1486
1487	file = status.File("qux/bar/baz")
1488	c.Assert(file.Staging, Equals, Added)
1489	c.Assert(file.Worktree, Equals, Unmodified)
1490}
1491
1492func (s *WorktreeSuite) TestAddGlobErrorNoMatches(c *C) {
1493	r, _ := Init(memory.NewStorage(), memfs.New())
1494	w, _ := r.Worktree()
1495
1496	err := w.AddGlob("foo")
1497	c.Assert(err, Equals, ErrGlobNoMatches)
1498}
1499
1500func (s *WorktreeSuite) TestRemove(c *C) {
1501	fs := memfs.New()
1502	w := &Worktree{
1503		r:          s.Repository,
1504		Filesystem: fs,
1505	}
1506
1507	err := w.Checkout(&CheckoutOptions{Force: true})
1508	c.Assert(err, IsNil)
1509
1510	hash, err := w.Remove("LICENSE")
1511	c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f")
1512	c.Assert(err, IsNil)
1513
1514	status, err := w.Status()
1515	c.Assert(err, IsNil)
1516	c.Assert(status, HasLen, 1)
1517	c.Assert(status.File("LICENSE").Staging, Equals, Deleted)
1518}
1519
1520func (s *WorktreeSuite) TestRemoveNotExistentEntry(c *C) {
1521	fs := memfs.New()
1522	w := &Worktree{
1523		r:          s.Repository,
1524		Filesystem: fs,
1525	}
1526
1527	err := w.Checkout(&CheckoutOptions{Force: true})
1528	c.Assert(err, IsNil)
1529
1530	hash, err := w.Remove("not-exists")
1531	c.Assert(hash.IsZero(), Equals, true)
1532	c.Assert(err, NotNil)
1533}
1534
1535func (s *WorktreeSuite) TestRemoveDirectory(c *C) {
1536	fs := memfs.New()
1537	w := &Worktree{
1538		r:          s.Repository,
1539		Filesystem: fs,
1540	}
1541
1542	err := w.Checkout(&CheckoutOptions{Force: true})
1543	c.Assert(err, IsNil)
1544
1545	hash, err := w.Remove("json")
1546	c.Assert(hash.IsZero(), Equals, true)
1547	c.Assert(err, IsNil)
1548
1549	status, err := w.Status()
1550	c.Assert(err, IsNil)
1551	c.Assert(status, HasLen, 2)
1552	c.Assert(status.File("json/long.json").Staging, Equals, Deleted)
1553	c.Assert(status.File("json/short.json").Staging, Equals, Deleted)
1554
1555	_, err = w.Filesystem.Stat("json")
1556	c.Assert(os.IsNotExist(err), Equals, true)
1557}
1558
1559func (s *WorktreeSuite) TestRemoveDirectoryUntracked(c *C) {
1560	fs := memfs.New()
1561	w := &Worktree{
1562		r:          s.Repository,
1563		Filesystem: fs,
1564	}
1565
1566	err := w.Checkout(&CheckoutOptions{Force: true})
1567	c.Assert(err, IsNil)
1568
1569	err = util.WriteFile(w.Filesystem, "json/foo", []byte("FOO"), 0755)
1570	c.Assert(err, IsNil)
1571
1572	hash, err := w.Remove("json")
1573	c.Assert(hash.IsZero(), Equals, true)
1574	c.Assert(err, IsNil)
1575
1576	status, err := w.Status()
1577	c.Assert(err, IsNil)
1578	c.Assert(status, HasLen, 3)
1579	c.Assert(status.File("json/long.json").Staging, Equals, Deleted)
1580	c.Assert(status.File("json/short.json").Staging, Equals, Deleted)
1581	c.Assert(status.File("json/foo").Staging, Equals, Untracked)
1582
1583	_, err = w.Filesystem.Stat("json")
1584	c.Assert(err, IsNil)
1585}
1586
1587func (s *WorktreeSuite) TestRemoveDeletedFromWorktree(c *C) {
1588	fs := memfs.New()
1589	w := &Worktree{
1590		r:          s.Repository,
1591		Filesystem: fs,
1592	}
1593
1594	err := w.Checkout(&CheckoutOptions{Force: true})
1595	c.Assert(err, IsNil)
1596
1597	err = fs.Remove("LICENSE")
1598	c.Assert(err, IsNil)
1599
1600	hash, err := w.Remove("LICENSE")
1601	c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f")
1602	c.Assert(err, IsNil)
1603
1604	status, err := w.Status()
1605	c.Assert(err, IsNil)
1606	c.Assert(status, HasLen, 1)
1607	c.Assert(status.File("LICENSE").Staging, Equals, Deleted)
1608}
1609
1610func (s *WorktreeSuite) TestRemoveGlob(c *C) {
1611	fs := memfs.New()
1612	w := &Worktree{
1613		r:          s.Repository,
1614		Filesystem: fs,
1615	}
1616
1617	err := w.Checkout(&CheckoutOptions{Force: true})
1618	c.Assert(err, IsNil)
1619
1620	err = w.RemoveGlob(w.Filesystem.Join("json", "l*"))
1621	c.Assert(err, IsNil)
1622
1623	status, err := w.Status()
1624	c.Assert(err, IsNil)
1625	c.Assert(status, HasLen, 1)
1626	c.Assert(status.File("json/long.json").Staging, Equals, Deleted)
1627}
1628
1629func (s *WorktreeSuite) TestRemoveGlobDirectory(c *C) {
1630	fs := memfs.New()
1631	w := &Worktree{
1632		r:          s.Repository,
1633		Filesystem: fs,
1634	}
1635
1636	err := w.Checkout(&CheckoutOptions{Force: true})
1637	c.Assert(err, IsNil)
1638
1639	err = w.RemoveGlob("js*")
1640	c.Assert(err, IsNil)
1641
1642	status, err := w.Status()
1643	c.Assert(err, IsNil)
1644	c.Assert(status, HasLen, 2)
1645	c.Assert(status.File("json/short.json").Staging, Equals, Deleted)
1646	c.Assert(status.File("json/long.json").Staging, Equals, Deleted)
1647
1648	_, err = w.Filesystem.Stat("json")
1649	c.Assert(os.IsNotExist(err), Equals, true)
1650}
1651
1652func (s *WorktreeSuite) TestRemoveGlobDirectoryDeleted(c *C) {
1653	fs := memfs.New()
1654	w := &Worktree{
1655		r:          s.Repository,
1656		Filesystem: fs,
1657	}
1658
1659	err := w.Checkout(&CheckoutOptions{Force: true})
1660	c.Assert(err, IsNil)
1661
1662	err = fs.Remove("json/short.json")
1663	c.Assert(err, IsNil)
1664
1665	err = util.WriteFile(w.Filesystem, "json/foo", []byte("FOO"), 0755)
1666	c.Assert(err, IsNil)
1667
1668	err = w.RemoveGlob("js*")
1669	c.Assert(err, IsNil)
1670
1671	status, err := w.Status()
1672	c.Assert(err, IsNil)
1673	c.Assert(status, HasLen, 3)
1674	c.Assert(status.File("json/short.json").Staging, Equals, Deleted)
1675	c.Assert(status.File("json/long.json").Staging, Equals, Deleted)
1676}
1677
1678func (s *WorktreeSuite) TestMove(c *C) {
1679	fs := memfs.New()
1680	w := &Worktree{
1681		r:          s.Repository,
1682		Filesystem: fs,
1683	}
1684
1685	err := w.Checkout(&CheckoutOptions{Force: true})
1686	c.Assert(err, IsNil)
1687
1688	hash, err := w.Move("LICENSE", "foo")
1689	c.Check(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f")
1690	c.Assert(err, IsNil)
1691
1692	status, err := w.Status()
1693	c.Assert(err, IsNil)
1694	c.Assert(status, HasLen, 2)
1695	c.Assert(status.File("LICENSE").Staging, Equals, Deleted)
1696	c.Assert(status.File("foo").Staging, Equals, Added)
1697
1698}
1699
1700func (s *WorktreeSuite) TestMoveNotExistentEntry(c *C) {
1701	fs := memfs.New()
1702	w := &Worktree{
1703		r:          s.Repository,
1704		Filesystem: fs,
1705	}
1706
1707	err := w.Checkout(&CheckoutOptions{Force: true})
1708	c.Assert(err, IsNil)
1709
1710	hash, err := w.Move("not-exists", "foo")
1711	c.Assert(hash.IsZero(), Equals, true)
1712	c.Assert(err, NotNil)
1713}
1714
1715func (s *WorktreeSuite) TestMoveToExistent(c *C) {
1716	fs := memfs.New()
1717	w := &Worktree{
1718		r:          s.Repository,
1719		Filesystem: fs,
1720	}
1721
1722	err := w.Checkout(&CheckoutOptions{Force: true})
1723	c.Assert(err, IsNil)
1724
1725	hash, err := w.Move(".gitignore", "LICENSE")
1726	c.Assert(hash.IsZero(), Equals, true)
1727	c.Assert(err, Equals, ErrDestinationExists)
1728}
1729
1730func (s *WorktreeSuite) TestClean(c *C) {
1731	fs := fixtures.ByTag("dirty").One().Worktree()
1732
1733	// Open the repo.
1734	fs, err := fs.Chroot("repo")
1735	c.Assert(err, IsNil)
1736	r, err := PlainOpen(fs.Root())
1737	c.Assert(err, IsNil)
1738
1739	wt, err := r.Worktree()
1740	c.Assert(err, IsNil)
1741
1742	// Status before cleaning.
1743	status, err := wt.Status()
1744	c.Assert(err, IsNil)
1745	c.Assert(len(status), Equals, 2)
1746
1747	err = wt.Clean(&CleanOptions{})
1748	c.Assert(err, IsNil)
1749
1750	// Status after cleaning.
1751	status, err = wt.Status()
1752	c.Assert(err, IsNil)
1753
1754	c.Assert(len(status), Equals, 1)
1755
1756	fi, err := fs.Lstat("pkgA")
1757	c.Assert(err, IsNil)
1758	c.Assert(fi.IsDir(), Equals, true)
1759
1760	// Clean with Dir: true.
1761	err = wt.Clean(&CleanOptions{Dir: true})
1762	c.Assert(err, IsNil)
1763
1764	status, err = wt.Status()
1765	c.Assert(err, IsNil)
1766
1767	c.Assert(len(status), Equals, 0)
1768
1769	// An empty dir should be deleted, as well.
1770	_, err = fs.Lstat("pkgA")
1771	c.Assert(err, ErrorMatches, ".*(no such file or directory.*|.*file does not exist)*.")
1772
1773}
1774
1775func (s *WorktreeSuite) TestAlternatesRepo(c *C) {
1776	fs := fixtures.ByTag("alternates").One().Worktree()
1777
1778	// Open 1st repo.
1779	rep1fs, err := fs.Chroot("rep1")
1780	c.Assert(err, IsNil)
1781	rep1, err := PlainOpen(rep1fs.Root())
1782	c.Assert(err, IsNil)
1783
1784	// Open 2nd repo.
1785	rep2fs, err := fs.Chroot("rep2")
1786	c.Assert(err, IsNil)
1787	rep2, err := PlainOpen(rep2fs.Root())
1788	c.Assert(err, IsNil)
1789
1790	// Get the HEAD commit from the main repo.
1791	h, err := rep1.Head()
1792	c.Assert(err, IsNil)
1793	commit1, err := rep1.CommitObject(h.Hash())
1794	c.Assert(err, IsNil)
1795
1796	// Get the HEAD commit from the shared repo.
1797	h, err = rep2.Head()
1798	c.Assert(err, IsNil)
1799	commit2, err := rep2.CommitObject(h.Hash())
1800	c.Assert(err, IsNil)
1801
1802	c.Assert(commit1.String(), Equals, commit2.String())
1803}
1804
1805func (s *WorktreeSuite) TestGrep(c *C) {
1806	cases := []struct {
1807		name           string
1808		options        GrepOptions
1809		wantResult     []GrepResult
1810		dontWantResult []GrepResult
1811		wantError      error
1812	}{
1813		{
1814			name: "basic word match",
1815			options: GrepOptions{
1816				Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
1817			},
1818			wantResult: []GrepResult{
1819				{
1820					FileName:   "go/example.go",
1821					LineNumber: 3,
1822					Content:    "import (",
1823					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1824				},
1825				{
1826					FileName:   "vendor/foo.go",
1827					LineNumber: 3,
1828					Content:    "import \"fmt\"",
1829					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1830				},
1831			},
1832		}, {
1833			name: "case insensitive match",
1834			options: GrepOptions{
1835				Patterns: []*regexp.Regexp{regexp.MustCompile(`(?i)IMport`)},
1836			},
1837			wantResult: []GrepResult{
1838				{
1839					FileName:   "go/example.go",
1840					LineNumber: 3,
1841					Content:    "import (",
1842					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1843				},
1844				{
1845					FileName:   "vendor/foo.go",
1846					LineNumber: 3,
1847					Content:    "import \"fmt\"",
1848					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1849				},
1850			},
1851		}, {
1852			name: "invert match",
1853			options: GrepOptions{
1854				Patterns:    []*regexp.Regexp{regexp.MustCompile("import")},
1855				InvertMatch: true,
1856			},
1857			dontWantResult: []GrepResult{
1858				{
1859					FileName:   "go/example.go",
1860					LineNumber: 3,
1861					Content:    "import (",
1862					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1863				},
1864				{
1865					FileName:   "vendor/foo.go",
1866					LineNumber: 3,
1867					Content:    "import \"fmt\"",
1868					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1869				},
1870			},
1871		}, {
1872			name: "match at a given commit hash",
1873			options: GrepOptions{
1874				Patterns:   []*regexp.Regexp{regexp.MustCompile("The MIT License")},
1875				CommitHash: plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
1876			},
1877			wantResult: []GrepResult{
1878				{
1879					FileName:   "LICENSE",
1880					LineNumber: 1,
1881					Content:    "The MIT License (MIT)",
1882					TreeName:   "b029517f6300c2da0f4b651b8642506cd6aaf45d",
1883				},
1884			},
1885			dontWantResult: []GrepResult{
1886				{
1887					FileName:   "go/example.go",
1888					LineNumber: 3,
1889					Content:    "import (",
1890					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1891				},
1892			},
1893		}, {
1894			name: "match for a given pathspec",
1895			options: GrepOptions{
1896				Patterns:  []*regexp.Regexp{regexp.MustCompile("import")},
1897				PathSpecs: []*regexp.Regexp{regexp.MustCompile("go/")},
1898			},
1899			wantResult: []GrepResult{
1900				{
1901					FileName:   "go/example.go",
1902					LineNumber: 3,
1903					Content:    "import (",
1904					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1905				},
1906			},
1907			dontWantResult: []GrepResult{
1908				{
1909					FileName:   "vendor/foo.go",
1910					LineNumber: 3,
1911					Content:    "import \"fmt\"",
1912					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1913				},
1914			},
1915		}, {
1916			name: "match at a given reference name",
1917			options: GrepOptions{
1918				Patterns:      []*regexp.Regexp{regexp.MustCompile("import")},
1919				ReferenceName: "refs/heads/master",
1920			},
1921			wantResult: []GrepResult{
1922				{
1923					FileName:   "go/example.go",
1924					LineNumber: 3,
1925					Content:    "import (",
1926					TreeName:   "refs/heads/master",
1927				},
1928			},
1929		}, {
1930			name: "ambiguous options",
1931			options: GrepOptions{
1932				Patterns:      []*regexp.Regexp{regexp.MustCompile("import")},
1933				CommitHash:    plumbing.NewHash("2d55a722f3c3ecc36da919dfd8b6de38352f3507"),
1934				ReferenceName: "somereferencename",
1935			},
1936			wantError: ErrHashOrReference,
1937		}, {
1938			name: "multiple patterns",
1939			options: GrepOptions{
1940				Patterns: []*regexp.Regexp{
1941					regexp.MustCompile("import"),
1942					regexp.MustCompile("License"),
1943				},
1944			},
1945			wantResult: []GrepResult{
1946				{
1947					FileName:   "go/example.go",
1948					LineNumber: 3,
1949					Content:    "import (",
1950					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1951				},
1952				{
1953					FileName:   "vendor/foo.go",
1954					LineNumber: 3,
1955					Content:    "import \"fmt\"",
1956					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1957				},
1958				{
1959					FileName:   "LICENSE",
1960					LineNumber: 1,
1961					Content:    "The MIT License (MIT)",
1962					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1963				},
1964			},
1965		}, {
1966			name: "multiple pathspecs",
1967			options: GrepOptions{
1968				Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
1969				PathSpecs: []*regexp.Regexp{
1970					regexp.MustCompile("go/"),
1971					regexp.MustCompile("vendor/"),
1972				},
1973			},
1974			wantResult: []GrepResult{
1975				{
1976					FileName:   "go/example.go",
1977					LineNumber: 3,
1978					Content:    "import (",
1979					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1980				},
1981				{
1982					FileName:   "vendor/foo.go",
1983					LineNumber: 3,
1984					Content:    "import \"fmt\"",
1985					TreeName:   "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
1986				},
1987			},
1988		},
1989	}
1990
1991	path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
1992	server, err := PlainClone(c.MkDir(), false, &CloneOptions{
1993		URL: path,
1994	})
1995	c.Assert(err, IsNil)
1996
1997	w, err := server.Worktree()
1998	c.Assert(err, IsNil)
1999
2000	for _, tc := range cases {
2001		gr, err := w.Grep(&tc.options)
2002		if tc.wantError != nil {
2003			c.Assert(err, Equals, tc.wantError)
2004		} else {
2005			c.Assert(err, IsNil)
2006		}
2007
2008		// Iterate through the results and check if the wanted result is present
2009		// in the got result.
2010		for _, wantResult := range tc.wantResult {
2011			found := false
2012			for _, gotResult := range gr {
2013				if wantResult == gotResult {
2014					found = true
2015					break
2016				}
2017			}
2018			if !found {
2019				c.Errorf("unexpected grep results for %q, expected result to contain: %v", tc.name, wantResult)
2020			}
2021		}
2022
2023		// Iterate through the results and check if the not wanted result is
2024		// present in the got result.
2025		for _, dontWantResult := range tc.dontWantResult {
2026			found := false
2027			for _, gotResult := range gr {
2028				if dontWantResult == gotResult {
2029					found = true
2030					break
2031				}
2032			}
2033			if found {
2034				c.Errorf("unexpected grep results for %q, expected result to NOT contain: %v", tc.name, dontWantResult)
2035			}
2036		}
2037	}
2038}
2039
2040func (s *WorktreeSuite) TestAddAndCommit(c *C) {
2041	dir, err := ioutil.TempDir("", "plain-repo")
2042	c.Assert(err, IsNil)
2043	defer os.RemoveAll(dir)
2044
2045	repo, err := PlainInit(dir, false)
2046	c.Assert(err, IsNil)
2047
2048	w, err := repo.Worktree()
2049	c.Assert(err, IsNil)
2050
2051	_, err = w.Add(".")
2052	c.Assert(err, IsNil)
2053
2054	w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{
2055		Name:  "foo",
2056		Email: "foo@foo.foo",
2057		When:  time.Now(),
2058	}})
2059
2060	iter, err := w.r.Log(&LogOptions{})
2061	c.Assert(err, IsNil)
2062	err = iter.ForEach(func(c *object.Commit) error {
2063		files, err := c.Files()
2064		if err != nil {
2065			return err
2066		}
2067
2068		err = files.ForEach(func(f *object.File) error {
2069			return errors.New("Expected no files, got at least 1")
2070		})
2071		return err
2072	})
2073	c.Assert(err, IsNil)
2074}
2075
2076func (s *WorktreeSuite) TestLinkedWorktree(c *C) {
2077	fs := fixtures.ByTag("linked-worktree").One().Worktree()
2078
2079	// Open main repo.
2080	{
2081		fs, err := fs.Chroot("main")
2082		c.Assert(err, IsNil)
2083		repo, err := PlainOpenWithOptions(fs.Root(), &PlainOpenOptions{EnableDotGitCommonDir: true})
2084		c.Assert(err, IsNil)
2085
2086		wt, err := repo.Worktree()
2087		c.Assert(err, IsNil)
2088
2089		status, err := wt.Status()
2090		c.Assert(err, IsNil)
2091		c.Assert(len(status), Equals, 2) // 2 files
2092
2093		head, err := repo.Head()
2094		c.Assert(err, IsNil)
2095		c.Assert(string(head.Name()), Equals, "refs/heads/master")
2096	}
2097
2098	// Open linked-worktree #1.
2099	{
2100		fs, err := fs.Chroot("linked-worktree-1")
2101		c.Assert(err, IsNil)
2102		repo, err := PlainOpenWithOptions(fs.Root(), &PlainOpenOptions{EnableDotGitCommonDir: true})
2103		c.Assert(err, IsNil)
2104
2105		wt, err := repo.Worktree()
2106		c.Assert(err, IsNil)
2107
2108		status, err := wt.Status()
2109		c.Assert(err, IsNil)
2110		c.Assert(len(status), Equals, 3) // 3 files
2111
2112		_, ok := status["linked-worktree-1-unique-file.txt"]
2113		c.Assert(ok, Equals, true)
2114
2115		head, err := repo.Head()
2116		c.Assert(err, IsNil)
2117		c.Assert(string(head.Name()), Equals, "refs/heads/linked-worktree-1")
2118	}
2119
2120	// Open linked-worktree #2.
2121	{
2122		fs, err := fs.Chroot("linked-worktree-2")
2123		c.Assert(err, IsNil)
2124		repo, err := PlainOpenWithOptions(fs.Root(), &PlainOpenOptions{EnableDotGitCommonDir: true})
2125		c.Assert(err, IsNil)
2126
2127		wt, err := repo.Worktree()
2128		c.Assert(err, IsNil)
2129
2130		status, err := wt.Status()
2131		c.Assert(err, IsNil)
2132		c.Assert(len(status), Equals, 3) // 3 files
2133
2134		_, ok := status["linked-worktree-2-unique-file.txt"]
2135		c.Assert(ok, Equals, true)
2136
2137		head, err := repo.Head()
2138		c.Assert(err, IsNil)
2139		c.Assert(string(head.Name()), Equals, "refs/heads/branch-with-different-name")
2140	}
2141
2142	// Open linked-worktree #2.
2143	{
2144		fs, err := fs.Chroot("linked-worktree-invalid-commondir")
2145		c.Assert(err, IsNil)
2146		_, err = PlainOpenWithOptions(fs.Root(), &PlainOpenOptions{EnableDotGitCommonDir: true})
2147		c.Assert(err, Equals, ErrRepositoryIncomplete)
2148	}
2149}
2150