github.com/hattya/nazuna@v0.7.1-0.20240331055452-55e14c275c1c/wc_test.go (about)

     1  //
     2  // nazuna :: wc_test.go
     3  //
     4  //   Copyright (c) 2013-2022 Akinori Hattori <hattya@gmail.com>
     5  //
     6  //   SPDX-License-Identifier: MIT
     7  //
     8  
     9  package nazuna_test
    10  
    11  import (
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/hattya/nazuna"
    20  )
    21  
    22  func TestOpenWC(t *testing.T) {
    23  	repo := init_(t)
    24  
    25  	wc, err := repo.WC()
    26  	if err != nil {
    27  		t.Fatal(err)
    28  	}
    29  	if err := wc.Flush(); err != nil {
    30  		t.Error(err)
    31  	}
    32  	data, err := os.ReadFile(filepath.Join(".nzn", "state.json"))
    33  	if err != nil {
    34  		t.Fatal(err)
    35  	}
    36  	if g, e := string(data), "{}\n"; g != e {
    37  		t.Errorf("expected %q, got %q", e, g)
    38  	}
    39  }
    40  
    41  func TestOpenWCError(t *testing.T) {
    42  	repo := init_(t)
    43  
    44  	// unmarshal error
    45  	if err := mkdir(".nzn", "state.json"); err != nil {
    46  		t.Fatal(err)
    47  	}
    48  	if _, err := repo.WC(); err == nil {
    49  		t.Error("expected error")
    50  	}
    51  }
    52  
    53  func TestWCPaths(t *testing.T) {
    54  	repo := init_(t)
    55  
    56  	wc, err := repo.WC()
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	if g, e := wc.PathFor("file"), filepath.Join(repo.Root(), "file"); g != e {
    61  		t.Errorf("WC.PathFor(%q) = %q, expected %q", "file", g, e)
    62  	}
    63  	if wc.Exists("file") {
    64  		t.Errorf("WC.Exists(%q) = true, expected false", "file")
    65  	}
    66  	// base: /
    67  	base := '/'
    68  	if rel, err := wc.Rel(base, filepath.Join(repo.Root(), "file")); err != nil {
    69  		t.Error(err)
    70  	} else if g, e := rel, "file"; g != e {
    71  		t.Errorf("WC.Rel('%q', ...) = %q, expected %q", base, g, e)
    72  	}
    73  	if rel, err := wc.Rel(base, "file"); err != nil {
    74  		t.Error(err)
    75  	} else if g, e := rel, "file"; g != e {
    76  		t.Errorf("WC.Rel('%q', ...) = %q, expected %q", base, g, e)
    77  	}
    78  	// base: .
    79  	base = '.'
    80  	if rel, err := wc.Rel(base, "file"); err != nil {
    81  		t.Error(err)
    82  	} else if g, e := rel, "file"; g != e {
    83  		t.Errorf("WC.Rel('%q', ...) = %q, expected %q", base, g, e)
    84  	}
    85  	if rel, err := wc.Rel(base, "$var"); err != nil {
    86  		t.Error(err)
    87  	} else if g, e := rel, "$var"; g != e {
    88  		t.Errorf("WC.Rel('%q', ...) = %q, expected %q", base, g, e)
    89  	}
    90  	// unknown base
    91  	if _, err := wc.Rel('_', ""); err == nil {
    92  		t.Error("expected error")
    93  	}
    94  	// not under root
    95  	if _, err := wc.Rel('/', filepath.Dir(repo.Root())); err == nil {
    96  		t.Error("expected error")
    97  	}
    98  	if _, err := wc.Rel('/', filepath.Join(filepath.Dir(repo.Root()), "file")); err == nil {
    99  		t.Error("expected error")
   100  	}
   101  }
   102  
   103  func TestWCLinks(t *testing.T) {
   104  	repo := init_(t)
   105  
   106  	wc, err := repo.WC()
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  	// file
   111  	dst := "link"
   112  	src := repo.PathFor(nil, dst)
   113  	if err := touch(src); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	if err := wc.Link(src, dst); err != nil {
   117  		t.Fatal(err)
   118  	}
   119  	if err := testLink(wc, src, dst); err != nil {
   120  		t.Error(err)
   121  	}
   122  	if err := wc.Unlink(dst); err != nil {
   123  		t.Fatal(err)
   124  	}
   125  	// file in directory
   126  	dst = filepath.Join("dir", "link")
   127  	src = repo.PathFor(nil, dst)
   128  	if err := mkdir(filepath.Dir(src)); err != nil {
   129  		t.Fatal(err)
   130  	}
   131  	if err := touch(src); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	if err := wc.Link(src, dst); err != nil {
   135  		t.Fatal(err)
   136  	}
   137  	if err := testLink(wc, src, dst); err != nil {
   138  		t.Error(err)
   139  	}
   140  	if err := wc.Unlink(dst); err != nil {
   141  		t.Fatal(err)
   142  	}
   143  	if _, err := os.Stat(filepath.Dir(dst)); err == nil {
   144  		t.Fatal("expected to remove parent directories")
   145  	}
   146  	// directory
   147  	dst = "dir"
   148  	src = repo.PathFor(nil, dst)
   149  	if err := wc.Link(src, dst); err != nil {
   150  		t.Fatal(err)
   151  	}
   152  	if err := testLink(wc, src, dst); err != nil {
   153  		t.Error(err)
   154  	}
   155  	if err := wc.Unlink(dst); err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	// keep non-empty directory
   159  	dst = filepath.Join("dir", "link")
   160  	src = repo.PathFor(nil, dst)
   161  	if err := wc.Link(src, dst); err != nil {
   162  		t.Fatal(err)
   163  	}
   164  	if err := touch(filepath.Join("dir", "file")); err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	if err := wc.Unlink(dst); err != nil {
   168  		t.Error(err)
   169  	}
   170  	if _, err := os.Stat("dir"); err != nil {
   171  		t.Error("expected to keep parent directories")
   172  	}
   173  	if err := os.RemoveAll("dir"); err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	// parent path is link
   177  	dst = "dir"
   178  	src = repo.PathFor(nil, dst)
   179  	if err := wc.Link(src, dst); err != nil {
   180  		t.Fatal(err)
   181  	}
   182  	dst = filepath.Join("dir", "file")
   183  	src = repo.PathFor(nil, dst)
   184  	switch err := wc.Link(src, dst).(type) {
   185  	case *os.PathError:
   186  		if g, e := err.Err, nazuna.ErrLink; g != e {
   187  			t.Errorf("expected %q, got %q", e, g)
   188  		}
   189  	default:
   190  		t.Errorf("expected *os.PathError, got %T", err)
   191  	}
   192  	if err := wc.Unlink(filepath.Join("dir", "file")); err == nil {
   193  		t.Error("expected error")
   194  	}
   195  	if err := wc.Unlink("dir"); err != nil {
   196  		t.Fatal(err)
   197  	}
   198  }
   199  
   200  func testLink(wc *nazuna.WC, src, dst string) error {
   201  	if !wc.IsLink(dst) {
   202  		return fmt.Errorf("wc.IsLink(%q) = false, expected true", dst)
   203  	}
   204  	if !wc.LinksTo(dst, src) {
   205  		return fmt.Errorf("wc.LinksTo(%q) = false, expected true", dst)
   206  	}
   207  	if err := wc.Link(src, dst); err == nil {
   208  		return fmt.Errorf("expected error")
   209  	}
   210  	return nil
   211  }
   212  
   213  func TestSelectLayer(t *testing.T) {
   214  	repo := init_(t)
   215  
   216  	wc, err := repo.WC()
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  	a, err := repo.NewLayer("a")
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  	b1, err := repo.NewLayer("b/1")
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	b2, err := repo.NewLayer("b/2")
   229  	if err != nil {
   230  		t.Fatal(err)
   231  	}
   232  	// cannot resolve
   233  	switch _, err := wc.LayerFor("b"); {
   234  	case err == nil:
   235  		t.Error("expected error")
   236  	case !strings.HasPrefix(err.Error(), "cannot resolve layer "):
   237  		t.Error("unexpected error:", err)
   238  	}
   239  	switch _, err := wc.Layers(); {
   240  	case err == nil:
   241  		t.Error("expected error")
   242  	case !strings.HasPrefix(err.Error(), "cannot resolve layer "):
   243  		t.Error("unexpected error:", err)
   244  	}
   245  	// cannot select
   246  	for _, s := range []string{"_", "a", "b"} {
   247  		if err := wc.SelectLayer(s); err == nil {
   248  			t.Errorf("%v: expected error", s)
   249  		}
   250  	}
   251  
   252  	if err := wc.SelectLayer(b2.Path()); err != nil {
   253  		t.Error(err)
   254  	}
   255  	if err := wc.SelectLayer(b1.Path()); err != nil {
   256  		t.Error(err)
   257  	}
   258  	if _, err := wc.LayerFor("b"); err != nil {
   259  		t.Error(err)
   260  	}
   261  	if ll, err := wc.Layers(); err != nil {
   262  		t.Error(err)
   263  	} else if g, e := ll, []*nazuna.Layer{b1, a}; !reflect.DeepEqual(g, e) {
   264  		t.Errorf("WC.Layers() = {%q, %q}, expected {%q, %q}", g[0].Path(), g[1].Path(), e[0].Path(), e[1].Path())
   265  	}
   266  	// already selected
   267  	if err := wc.SelectLayer(b1.Path()); err == nil {
   268  		t.Error("expected error")
   269  	}
   270  }
   271  
   272  func TestMergeLayers(t *testing.T) {
   273  	repo := init_(t)
   274  
   275  	wc, err := repo.WC()
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	a, err := repo.NewLayer("a")
   280  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  	b1, err := repo.NewLayer("b/1")
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	b2, err := repo.NewLayer("b/2")
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  
   292  	ui := new(testUI)
   293  	git, err := nazuna.VCSFor(ui, filepath.Join(".nzn", "r"))
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  	alias := func(l *nazuna.Layer, src, dst string) {
   298  		t.Helper()
   299  		if err := l.NewAlias(src, dst); err != nil {
   300  			t.Fatal(err)
   301  		}
   302  	}
   303  	file := func(l *nazuna.Layer, n string) {
   304  		t.Helper()
   305  		if err := mkdir(filepath.Dir(repo.PathFor(l, n))); err != nil {
   306  			t.Fatal(err)
   307  		}
   308  		if err := touch(repo.PathFor(l, n)); err != nil {
   309  			t.Fatal(err)
   310  		}
   311  	}
   312  	link := func(l *nazuna.Layer, src, dst string) {
   313  		t.Helper()
   314  		if _, err := l.NewLink(nil, repo.PathFor(l, src), dst+"a"); err != nil {
   315  			t.Fatal(err)
   316  		}
   317  		if _, err := l.NewLink([]string{repo.PathFor(l, ".")}, src, dst+"b"); err != nil {
   318  			t.Fatal(err)
   319  		}
   320  	}
   321  	subrepo := func(l *nazuna.Layer, dst string) {
   322  		t.Helper()
   323  		src := "github.com/hattya/" + filepath.Base(dst)
   324  		if _, err := l.NewSubrepo(src+"a", dst+"a"); err != nil {
   325  			t.Fatal(err)
   326  		}
   327  		repo, err := l.NewSubrepo(src+"b", dst+"b")
   328  		if err != nil {
   329  			t.Fatal(err)
   330  		}
   331  		repo.Name = filepath.Base(repo.Src)
   332  	}
   333  	// file
   334  	file(a, "file1")
   335  	// file :: → alias
   336  	file(a, "file2")
   337  	alias(b1, "file2", "file2_")
   338  	alias(b2, "file2", "file2_")
   339  	// :: file
   340  	file(b1, "file3")
   341  	file(b2, "file3")
   342  	// dir
   343  	file(a, filepath.Join("dir1", "file1"))
   344  	// dir/file
   345  	file(a, filepath.Join("dir2", "file1"))
   346  	// file :: → alias
   347  	file(a, "file4")
   348  	alias(b1, "file4", filepath.Join("dir2", "file2"))
   349  	alias(b2, "file4", filepath.Join("dir2", "file2"))
   350  	// dir/dir/file
   351  	file(a, filepath.Join("dir2", "dir1", "file1"))
   352  	// file :: → alias
   353  	file(a, "file5")
   354  	alias(b1, "file5", filepath.Join("dir2", "dir1", "file2"))
   355  	alias(b2, "file5", filepath.Join("dir2", "dir1", "file2"))
   356  	// dir/[file :: → alias]
   357  	file(a, filepath.Join("dir2", "file3"))
   358  	alias(b1, filepath.Join("dir2", "file3"), filepath.Join("dir2", "file3_"))
   359  	alias(b2, filepath.Join("dir2", "file3"), filepath.Join("dir2", "file3_"))
   360  	// dir/file :: → alias
   361  	file(a, filepath.Join("dir3", "file1"))
   362  	alias(b1, filepath.Join("dir3", "file1"), filepath.Join("dir2", "file4"))
   363  	alias(b2, filepath.Join("dir3", "file1"), filepath.Join("dir2", "file4"))
   364  	// [dir :: → alias]/file
   365  	file(a, filepath.Join("dir4", "file5"))
   366  	alias(b1, "dir4", "dir2")
   367  	alias(b2, "dir4", "dir2")
   368  	// :: dir/file
   369  	file(b1, filepath.Join("dir2", "file6"))
   370  	file(b2, filepath.Join("dir2", "file6"))
   371  	// dir/file :: → alias
   372  	file(a, filepath.Join("dir5", "file1"))
   373  	alias(b1, filepath.Join("dir5", "file1"), "file6")
   374  	alias(b2, filepath.Join("dir5", "file1"), "file6")
   375  	// dir exists
   376  	if err := mkdir(wc.PathFor("dir6")); err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	file(a, filepath.Join("dir6", "file1"))
   380  	// link
   381  	link(a, "file1", "link1")
   382  	// link :: → alias
   383  	link(a, "file2", "link2")
   384  	alias(b1, "link2a", "link2a_")
   385  	alias(b1, "link2b", "link2b_")
   386  	alias(b2, "link2a", "link2a_")
   387  	alias(b2, "link2b", "link2b_")
   388  	// :: link
   389  	link(b1, "file3", "link3")
   390  	link(b2, "file3", "link3")
   391  	// link :: → alias
   392  	link(a, "file4", "link4")
   393  	alias(b1, "link4a", filepath.Join("dir2", "link2a"))
   394  	alias(b1, "link4b", filepath.Join("dir2", "link2b"))
   395  	alias(b2, "link4a", filepath.Join("dir2", "link2a"))
   396  	alias(b2, "link4b", filepath.Join("dir2", "link2b"))
   397  	// dir/[link :: → alias]
   398  	link(a, filepath.Join("dir2", "file3"), filepath.Join("dir2", "link3"))
   399  	alias(b1, filepath.Join("dir2", "link3a"), filepath.Join("dir2", "link3a_"))
   400  	alias(b1, filepath.Join("dir2", "link3b"), filepath.Join("dir2", "link3b_"))
   401  	alias(b2, filepath.Join("dir2", "link3a"), filepath.Join("dir2", "link3a_"))
   402  	alias(b2, filepath.Join("dir2", "link3b"), filepath.Join("dir2", "link3b_"))
   403  	// dir/link :: → alias
   404  	link(a, filepath.Join("dir3", "file1"), filepath.Join("dir3", "link1"))
   405  	alias(b1, filepath.Join("dir3", "link1a"), filepath.Join("dir2", "link4a"))
   406  	alias(b1, filepath.Join("dir3", "link1b"), filepath.Join("dir2", "link4b"))
   407  	alias(b2, filepath.Join("dir3", "link1a"), filepath.Join("dir2", "link4a"))
   408  	alias(b2, filepath.Join("dir3", "link1b"), filepath.Join("dir2", "link4b"))
   409  	// [dir :: → alias]/link
   410  	link(a, filepath.Join("dir4", "file5"), filepath.Join("dir4", "link5"))
   411  	// :: dir/link
   412  	link(b1, filepath.Join("dir2", "file6"), filepath.Join("dir2", "link6"))
   413  	link(b2, filepath.Join("dir2", "file6"), filepath.Join("dir2", "link6"))
   414  	// dir/link :: → alias
   415  	link(a, filepath.Join("dir5", "file1"), filepath.Join("dir5", "link1"))
   416  	alias(b1, filepath.Join("dir5", "link1a"), "link6a")
   417  	alias(b1, filepath.Join("dir5", "link1b"), "link6b")
   418  	alias(b2, filepath.Join("dir5", "link1a"), "link6a")
   419  	alias(b2, filepath.Join("dir5", "link1b"), "link6b")
   420  	// subrepo
   421  	subrepo(a, "repo1")
   422  	// subrepo :: → alias
   423  	subrepo(a, "repo2")
   424  	alias(b1, "repo2a", "repo2a_")
   425  	alias(b1, "repo2b", "repo2b_")
   426  	alias(b2, "repo2a", "repo2a_")
   427  	alias(b2, "repo2b", "repo2b_")
   428  	// :: subrepo
   429  	subrepo(b1, "repo3")
   430  	subrepo(b2, "repo3")
   431  	// subrepo :: → alias
   432  	subrepo(a, "repo4")
   433  	alias(b1, "repo4a", filepath.Join("dir2", "repo1a"))
   434  	alias(b1, "repo4b", filepath.Join("dir2", "repo1b"))
   435  	alias(b2, "repo4a", filepath.Join("dir2", "repo1a"))
   436  	alias(b2, "repo4b", filepath.Join("dir2", "repo1b"))
   437  	// dir[subrepo :: → alias]
   438  	subrepo(a, filepath.Join("dir2", "repo3"))
   439  	alias(b1, filepath.Join("dir2", "repo3a"), filepath.Join("dir2", "repo3a_"))
   440  	alias(b1, filepath.Join("dir2", "repo3b"), filepath.Join("dir2", "repo3b_"))
   441  	alias(b2, filepath.Join("dir2", "repo3a"), filepath.Join("dir2", "repo3a_"))
   442  	alias(b2, filepath.Join("dir2", "repo3b"), filepath.Join("dir2", "repo3b_"))
   443  	// dir/subrepo → alias
   444  	subrepo(a, filepath.Join("dir3", "repo1"))
   445  	alias(b1, filepath.Join("dir3", "repo1a"), filepath.Join("dir2", "repo4a"))
   446  	alias(b1, filepath.Join("dir3", "repo1b"), filepath.Join("dir2", "repo4b"))
   447  	alias(b2, filepath.Join("dir3", "repo1a"), filepath.Join("dir2", "repo4a"))
   448  	alias(b2, filepath.Join("dir3", "repo1b"), filepath.Join("dir2", "repo4b"))
   449  	// [dir :: → alias]/subrepo
   450  	subrepo(a, filepath.Join("dir4", "repo5"))
   451  	// :: dir/subrepo
   452  	subrepo(b1, filepath.Join("dir2", "repo6"))
   453  	subrepo(b2, filepath.Join("dir2", "repo6"))
   454  	// dir/subrepo :: → alias
   455  	subrepo(a, filepath.Join("dir5", "repo1"))
   456  	alias(b1, filepath.Join("dir5", "repo1a"), "repo6a")
   457  	alias(b1, filepath.Join("dir5", "repo1b"), "repo6b")
   458  	alias(b2, filepath.Join("dir5", "repo1a"), "repo6a")
   459  	alias(b2, filepath.Join("dir5", "repo1b"), "repo6b")
   460  
   461  	if err := git.Add("."); err != nil {
   462  		t.Fatal(err)
   463  	}
   464  
   465  	wc.State.WC = []*nazuna.Entry{
   466  		{
   467  			Layer: a.Path(),
   468  			Path:  "dir2",
   469  			IsDir: true,
   470  		},
   471  	}
   472  	e := []*nazuna.Entry{
   473  		{
   474  			Layer: a.Path(),
   475  			Path:  "dir1",
   476  			IsDir: true,
   477  		},
   478  		{
   479  			Layer: a.Path(),
   480  			Path:  "dir2/dir1/file1",
   481  		},
   482  		{
   483  			Layer:  a.Path(),
   484  			Path:   "dir2/dir1/file2",
   485  			Origin: "file5",
   486  		},
   487  		{
   488  			Layer: a.Path(),
   489  			Path:  "dir2/file1",
   490  		},
   491  		{
   492  			Layer:  a.Path(),
   493  			Path:   "dir2/file2",
   494  			Origin: "file4",
   495  		},
   496  		{
   497  			Layer:  a.Path(),
   498  			Path:   "dir2/file3_",
   499  			Origin: "dir2/file3",
   500  		},
   501  		{
   502  			Layer:  a.Path(),
   503  			Path:   "dir2/file4",
   504  			Origin: "dir3/file1",
   505  		},
   506  		{
   507  			Layer:  a.Path(),
   508  			Path:   "dir2/file5",
   509  			Origin: "dir4/file5",
   510  		},
   511  		{
   512  			Layer: b1.Path(),
   513  			Path:  "dir2/file6",
   514  		},
   515  		{
   516  			Layer:  a.Path(),
   517  			Path:   "dir2/link2a",
   518  			Origin: repo.PathFor(a, "file4"),
   519  			Type:   "link",
   520  		},
   521  		{
   522  			Layer:  a.Path(),
   523  			Path:   "dir2/link2b",
   524  			Origin: repo.PathFor(a, "file4"),
   525  			Type:   "link",
   526  		},
   527  		{
   528  			Layer:  a.Path(),
   529  			Path:   "dir2/link3a_",
   530  			Origin: repo.PathFor(a, filepath.Join("dir2", "file3")),
   531  			Type:   "link",
   532  		},
   533  		{
   534  			Layer:  a.Path(),
   535  			Path:   "dir2/link3b_",
   536  			Origin: repo.PathFor(a, filepath.Join("dir2", "file3")),
   537  			Type:   "link",
   538  		},
   539  		{
   540  			Layer:  a.Path(),
   541  			Path:   "dir2/link4a",
   542  			Origin: repo.PathFor(a, filepath.Join("dir3", "file1")),
   543  			Type:   "link",
   544  		},
   545  		{
   546  			Layer:  a.Path(),
   547  			Path:   "dir2/link4b",
   548  			Origin: repo.PathFor(a, filepath.Join("dir3", "file1")),
   549  			Type:   "link",
   550  		},
   551  		{
   552  			Layer:  a.Path(),
   553  			Path:   "dir2/link5a",
   554  			Origin: repo.PathFor(a, filepath.Join("dir4", "file5")),
   555  			Type:   "link",
   556  		},
   557  		{
   558  			Layer:  a.Path(),
   559  			Path:   "dir2/link5b",
   560  			Origin: repo.PathFor(a, filepath.Join("dir4", "file5")),
   561  			Type:   "link",
   562  		},
   563  		{
   564  			Layer:  b1.Path(),
   565  			Path:   "dir2/link6a",
   566  			Origin: repo.PathFor(b1, filepath.Join("dir2", "file6")),
   567  			Type:   "link",
   568  		},
   569  		{
   570  			Layer:  b1.Path(),
   571  			Path:   "dir2/link6b",
   572  			Origin: repo.PathFor(b1, filepath.Join("dir2", "file6")),
   573  			Type:   "link",
   574  		},
   575  		{
   576  			Layer:  a.Path(),
   577  			Path:   "dir2/repo1a",
   578  			Origin: "github.com/hattya/repo4a",
   579  			Type:   "subrepo",
   580  		},
   581  		{
   582  			Layer:  a.Path(),
   583  			Path:   "dir2/repo1b",
   584  			Origin: "github.com/hattya/repo4b",
   585  			Type:   "subrepo",
   586  		},
   587  		{
   588  			Layer:  a.Path(),
   589  			Path:   "dir2/repo3a_",
   590  			Origin: "github.com/hattya/repo3a",
   591  			Type:   "subrepo",
   592  		},
   593  		{
   594  			Layer:  a.Path(),
   595  			Path:   "dir2/repo3b_",
   596  			Origin: "github.com/hattya/repo3b",
   597  			Type:   "subrepo",
   598  		},
   599  		{
   600  			Layer:  a.Path(),
   601  			Path:   "dir2/repo4a",
   602  			Origin: "github.com/hattya/repo1a",
   603  			Type:   "subrepo",
   604  		},
   605  		{
   606  			Layer:  a.Path(),
   607  			Path:   "dir2/repo4b",
   608  			Origin: "github.com/hattya/repo1b",
   609  			Type:   "subrepo",
   610  		},
   611  		{
   612  			Layer:  a.Path(),
   613  			Path:   "dir2/repo5a",
   614  			Origin: "github.com/hattya/repo5a",
   615  			Type:   "subrepo",
   616  		},
   617  		{
   618  			Layer:  a.Path(),
   619  			Path:   "dir2/repo5b",
   620  			Origin: "github.com/hattya/repo5b",
   621  			Type:   "subrepo",
   622  		},
   623  		{
   624  			Layer:  b1.Path(),
   625  			Path:   "dir2/repo6a",
   626  			Origin: "github.com/hattya/repo6a",
   627  			Type:   "subrepo",
   628  		},
   629  		{
   630  			Layer:  b1.Path(),
   631  			Path:   "dir2/repo6b",
   632  			Origin: "github.com/hattya/repo6b",
   633  			Type:   "subrepo",
   634  		},
   635  		{
   636  			Layer: a.Path(),
   637  			Path:  "dir6/file1",
   638  		},
   639  		{
   640  			Layer: a.Path(),
   641  			Path:  "file1",
   642  		},
   643  		{
   644  			Layer:  a.Path(),
   645  			Path:   "file2_",
   646  			Origin: "file2",
   647  		},
   648  		{
   649  			Layer: b1.Path(),
   650  			Path:  "file3",
   651  		},
   652  		{
   653  			Layer:  a.Path(),
   654  			Path:   "file6",
   655  			Origin: "dir5/file1",
   656  		},
   657  		{
   658  			Layer:  a.Path(),
   659  			Path:   "link1a",
   660  			Origin: repo.PathFor(a, "file1"),
   661  			Type:   "link",
   662  		},
   663  		{
   664  			Layer:  a.Path(),
   665  			Path:   "link1b",
   666  			Origin: repo.PathFor(a, "file1"),
   667  			Type:   "link",
   668  		},
   669  		{
   670  			Layer:  a.Path(),
   671  			Path:   "link2a_",
   672  			Origin: repo.PathFor(a, "file2"),
   673  			Type:   "link",
   674  		},
   675  		{
   676  			Layer:  a.Path(),
   677  			Path:   "link2b_",
   678  			Origin: repo.PathFor(a, "file2"),
   679  			Type:   "link",
   680  		},
   681  		{
   682  			Layer:  b1.Path(),
   683  			Path:   "link3a",
   684  			Origin: repo.PathFor(b1, "file3"),
   685  			Type:   "link",
   686  		},
   687  		{
   688  			Layer:  b1.Path(),
   689  			Path:   "link3b",
   690  			Origin: repo.PathFor(b1, "file3"),
   691  			Type:   "link",
   692  		},
   693  		{
   694  			Layer:  a.Path(),
   695  			Path:   "link6a",
   696  			Origin: repo.PathFor(a, filepath.Join("dir5", "file1")),
   697  			Type:   "link",
   698  		},
   699  		{
   700  			Layer:  a.Path(),
   701  			Path:   "link6b",
   702  			Origin: repo.PathFor(a, filepath.Join("dir5", "file1")),
   703  			Type:   "link",
   704  		},
   705  		{
   706  			Layer:  a.Path(),
   707  			Path:   "repo1a",
   708  			Origin: "github.com/hattya/repo1a",
   709  			Type:   "subrepo",
   710  		},
   711  		{
   712  			Layer:  a.Path(),
   713  			Path:   "repo1b",
   714  			Origin: "github.com/hattya/repo1b",
   715  			Type:   "subrepo",
   716  		},
   717  		{
   718  			Layer:  a.Path(),
   719  			Path:   "repo2a_",
   720  			Origin: "github.com/hattya/repo2a",
   721  			Type:   "subrepo",
   722  		},
   723  		{
   724  			Layer:  a.Path(),
   725  			Path:   "repo2b_",
   726  			Origin: "github.com/hattya/repo2b",
   727  			Type:   "subrepo",
   728  		},
   729  		{
   730  			Layer:  b1.Path(),
   731  			Path:   "repo3a",
   732  			Origin: "github.com/hattya/repo3a",
   733  			Type:   "subrepo",
   734  		},
   735  		{
   736  			Layer:  b1.Path(),
   737  			Path:   "repo3b",
   738  			Origin: "github.com/hattya/repo3b",
   739  			Type:   "subrepo",
   740  		},
   741  		{
   742  			Layer:  a.Path(),
   743  			Path:   "repo6a",
   744  			Origin: "github.com/hattya/repo1a",
   745  			Type:   "subrepo",
   746  		},
   747  		{
   748  			Layer:  a.Path(),
   749  			Path:   "repo6b",
   750  			Origin: "github.com/hattya/repo1b",
   751  			Type:   "subrepo",
   752  		},
   753  	}
   754  	if err := wc.SelectLayer(b1.Path()); err != nil {
   755  		t.Fatal(err)
   756  	}
   757  	switch _, err := wc.MergeLayers(); {
   758  	case err != nil:
   759  		t.Error(err)
   760  	case !reflect.DeepEqual(wc.State.WC, e):
   761  		t.Error("unexpected result")
   762  	}
   763  
   764  	if err := mkdir(wc.PathFor("dir1")); err != nil {
   765  		t.Fatal(err)
   766  	}
   767  	e[0].Path = "dir1/file1"
   768  	e[0].IsDir = false
   769  	for i := range e {
   770  		if e[i].Layer == b1.Path() {
   771  			e[i].Layer = b2.Path()
   772  			if e[i].Type == "link" {
   773  				e[i].Origin = repo.PathFor(b2, e[i].Origin[len(repo.PathFor(b1, "."))+1:])
   774  			}
   775  		}
   776  	}
   777  	if err := wc.SelectLayer(b2.Path()); err != nil {
   778  		t.Fatal(err)
   779  	}
   780  	switch _, err := wc.MergeLayers(); {
   781  	case err != nil:
   782  		t.Error(err)
   783  	case !reflect.DeepEqual(wc.State.WC, e):
   784  		t.Error("unexpected result")
   785  	}
   786  }
   787  
   788  func TestMergeLayersError(t *testing.T) {
   789  	repo := init_(t)
   790  
   791  	wc, err := repo.WC()
   792  	if err != nil {
   793  		t.Fatal(err)
   794  	}
   795  	a, err := repo.NewLayer("a")
   796  	if err != nil {
   797  		t.Fatal(err)
   798  	}
   799  	b1, err := repo.NewLayer("b/1")
   800  	if err != nil {
   801  		t.Fatal(err)
   802  	}
   803  	if _, err := repo.NewLayer("b/2"); err != nil {
   804  		t.Fatal(err)
   805  	}
   806  	// cannot resolve
   807  	if _, err := wc.MergeLayers(); err == nil {
   808  		t.Error("expected error")
   809  	}
   810  
   811  	ui := new(testUI)
   812  	git, err := nazuna.VCSFor(ui, filepath.Join(".nzn", "r"))
   813  	if err != nil {
   814  		t.Fatal(err)
   815  	}
   816  	reset := func() {
   817  		a.Links = nil
   818  		a.Subrepos = nil
   819  		b1.Aliases = nil
   820  	}
   821  	alias := func(l *nazuna.Layer, src, dst string) {
   822  		t.Helper()
   823  		if err := l.NewAlias(src, dst); err != nil {
   824  			t.Fatal(err)
   825  		}
   826  	}
   827  	link := func(l *nazuna.Layer, path []string, src, dst string) {
   828  		t.Helper()
   829  		if _, err := l.NewLink(path, src, dst); err != nil {
   830  			t.Fatal(err)
   831  		}
   832  	}
   833  	subrepo := func(l *nazuna.Layer, dst string) {
   834  		t.Helper()
   835  		if _, err := l.NewSubrepo("github.com/hattya/"+filepath.Base(dst), dst); err != nil {
   836  			t.Fatal(err)
   837  		}
   838  	}
   839  	if err := touch(repo.PathFor(a, "file1")); err != nil {
   840  		t.Fatal(err)
   841  	}
   842  	if err := touch(repo.PathFor(a, "file2")); err != nil {
   843  		t.Fatal(err)
   844  	}
   845  	if err := git.Add("."); err != nil {
   846  		t.Fatal(err)
   847  	}
   848  	if err := wc.SelectLayer(b1.Path()); err != nil {
   849  		t.Fatal(err)
   850  	}
   851  	// file: file not found
   852  	if err := os.Remove(repo.PathFor(a, "file2")); err != nil {
   853  		t.Fatal(err)
   854  	}
   855  	if _, err := wc.MergeLayers(); err == nil {
   856  		t.Error("expected error")
   857  	}
   858  	if err := touch(repo.PathFor(a, "file2")); err != nil {
   859  		t.Fatal(err)
   860  	}
   861  	// file: alias error
   862  	alias(b1, "file1", filepath.Join("..", "file01"))
   863  	if _, err := wc.MergeLayers(); err == nil {
   864  		t.Error("expected error")
   865  	}
   866  	// link: file not found
   867  	reset()
   868  	link(a, nil, repo.PathFor(a, "file3"), "file03")
   869  	if _, err := wc.MergeLayers(); err != nil {
   870  		t.Error(err)
   871  	}
   872  	// link: alias error
   873  	reset()
   874  	link(a, nil, repo.PathFor(a, "file1"), "file01")
   875  	alias(b1, "file01", filepath.Join("..", "file001"))
   876  	if _, err := wc.MergeLayers(); err == nil {
   877  		t.Error("expected error")
   878  	}
   879  	// link: alias error
   880  	reset()
   881  	link(a, []string{repo.PathFor(a, ".")}, "file1", "file01")
   882  	alias(b1, "file01", filepath.Join("..", "file001"))
   883  	if _, err := wc.MergeLayers(); err == nil {
   884  		t.Error("expected error")
   885  	}
   886  	// link: warning
   887  	reset()
   888  	a.Links = map[string][]*nazuna.Link{
   889  		"": {
   890  			{
   891  				Src: repo.PathFor(a, "file1"),
   892  				Dst: "file1",
   893  			},
   894  		},
   895  	}
   896  	if _, err := wc.MergeLayers(); err != nil {
   897  		t.Error(err)
   898  	}
   899  	// subrepo: alias error
   900  	reset()
   901  	subrepo(a, "repo1")
   902  	alias(b1, "repo1", filepath.Join("..", "repo01"))
   903  	if _, err := wc.MergeLayers(); err == nil {
   904  		t.Error("expected error")
   905  	}
   906  	// subrepo: warning
   907  	reset()
   908  	a.Subrepos = map[string][]*nazuna.Subrepo{
   909  		"": {
   910  			{
   911  				Src:  "github.com/hattya/repo1",
   912  				Name: "file1",
   913  			},
   914  		},
   915  	}
   916  	if _, err := wc.MergeLayers(); err != nil {
   917  		t.Error(err)
   918  	}
   919  }
   920  
   921  func TestWCErrorf(t *testing.T) {
   922  	repo := init_(t)
   923  
   924  	wc, err := repo.WC()
   925  	if err != nil {
   926  		t.Fatal(err)
   927  	}
   928  
   929  	err = fmt.Errorf("error")
   930  	if g, e := wc.Errorf(err).Error(), "error"; g != e {
   931  		t.Errorf("expected %q, got %q", e, g)
   932  	}
   933  
   934  	err = &os.LinkError{
   935  		New: filepath.Join(repo.Root(), "link"),
   936  		Err: fmt.Errorf("link error"),
   937  	}
   938  	if g, e := wc.Errorf(err).Error(), "link: link error"; g != e {
   939  		t.Errorf("expected %q, got %q", e, g)
   940  	}
   941  
   942  	err = &os.PathError{
   943  		Path: filepath.Join(repo.Root(), "link"),
   944  		Err:  fmt.Errorf("path error"),
   945  	}
   946  	if g, e := wc.Errorf(err).Error(), "link: path error"; g != e {
   947  		t.Errorf("expected %q, got %q", e, g)
   948  	}
   949  }
   950  
   951  var entryTests = []struct {
   952  	e *nazuna.Entry
   953  	s string
   954  }{
   955  	{
   956  		&nazuna.Entry{},
   957  		"!",
   958  	},
   959  	{
   960  		&nazuna.Entry{
   961  			IsDir: true,
   962  		},
   963  		"!",
   964  	},
   965  	{
   966  		&nazuna.Entry{
   967  			Layer: "layer",
   968  		},
   969  		"!layer",
   970  	},
   971  	{
   972  		&nazuna.Entry{
   973  			Layer: "layer",
   974  			IsDir: true,
   975  		},
   976  		"!layer",
   977  	},
   978  	{
   979  		&nazuna.Entry{
   980  			Path: "path",
   981  		},
   982  		"path!",
   983  	},
   984  	{
   985  		&nazuna.Entry{
   986  			Path:  "path",
   987  			IsDir: true,
   988  		},
   989  		"path/!",
   990  	},
   991  	{
   992  		&nazuna.Entry{
   993  			Layer: "layer",
   994  			Path:  "path",
   995  		},
   996  		"path!layer",
   997  	},
   998  	{
   999  		&nazuna.Entry{
  1000  			Layer: "layer",
  1001  			Path:  "path",
  1002  			IsDir: true,
  1003  		},
  1004  		"path/!layer",
  1005  	},
  1006  	{
  1007  		&nazuna.Entry{
  1008  			Layer:  "layer",
  1009  			Path:   "path",
  1010  			Origin: "origin",
  1011  		},
  1012  		"path!layer:origin",
  1013  	},
  1014  	{
  1015  		&nazuna.Entry{
  1016  			Layer:  "layer",
  1017  			Path:   "path",
  1018  			Origin: "origin",
  1019  			IsDir:  true,
  1020  		},
  1021  		"path/!layer:origin/",
  1022  	},
  1023  	{
  1024  		&nazuna.Entry{
  1025  			Layer:  "layer",
  1026  			Path:   "path",
  1027  			Origin: "origin",
  1028  			Type:   "link",
  1029  		},
  1030  		"path!origin",
  1031  	},
  1032  	{
  1033  		&nazuna.Entry{
  1034  			Layer:  "layer",
  1035  			Path:   "path",
  1036  			Origin: "origin",
  1037  			IsDir:  true,
  1038  			Type:   "link",
  1039  		},
  1040  		"path/!origin" + string(os.PathSeparator),
  1041  	},
  1042  	{
  1043  		&nazuna.Entry{
  1044  			Layer:  "layer",
  1045  			Path:   "path",
  1046  			Origin: "github.com/hattya/nazuna",
  1047  			Type:   "subrepo",
  1048  		},
  1049  		"path!github.com/hattya/nazuna",
  1050  	},
  1051  	{
  1052  		&nazuna.Entry{
  1053  			Layer:  "layer",
  1054  			Path:   "path",
  1055  			Origin: "github.com/hattya/nazuna",
  1056  			IsDir:  true,
  1057  			Type:   "subrepo",
  1058  		},
  1059  		"path/!github.com/hattya/nazuna",
  1060  	},
  1061  }
  1062  
  1063  func TestEntry(t *testing.T) {
  1064  	for _, tt := range entryTests {
  1065  		if g, e := tt.e.Format("%v!%v"), tt.s; g != e {
  1066  			t.Errorf("expected %q, got %q", e, g)
  1067  		}
  1068  	}
  1069  }