github.com/samcontesse/bitbucket-cascade-merge@v0.0.0-20230227091349-c5ec053235b5/git_test.go (about)

     1  package main
     2  
     3  import (
     4  	"github.com/libgit2/git2go/v34"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"reflect"
     9  	"testing"
    10  	"time"
    11  )
    12  
    13  func TestCascadeMerge(t *testing.T) {
    14  
    15  	var path = filepath.Join(os.TempDir(), "cascade-"+time.Nanosecond.String()+".git")
    16  	os.RemoveAll(path)
    17  
    18  	// we need a bare repository in our tests because libgit2 does not support local push to non bare repository yet.
    19  	bare, err := git.InitRepository(path, true)
    20  	CheckFatal(err, t)
    21  	defer os.RemoveAll(path)
    22  	defer bare.Free()
    23  
    24  	err = WorkOnBareRepository(bare,
    25  		&InitializeWithReadmeTask{
    26  			t: t,
    27  		},
    28  		&CreateDummyFileOnBranchTask{
    29  			BranchName: "release/48",
    30  			Filename:   "foo",
    31  			t:          t,
    32  		},
    33  		&CreateDummyFileOnBranchTask{
    34  			BranchName: "release/49",
    35  			Filename:   "bar",
    36  			t:          t,
    37  		},
    38  		&CreateDummyFileOnBranchTask{
    39  			BranchName: "develop",
    40  			Filename:   "baz",
    41  			t:          t,
    42  		},
    43  	)
    44  	CheckFatal(err, t)
    45  
    46  	t.Run("NoConflict", CascadeNoConflict(bare))
    47  	t.Run("Conflict", CascadeConflict(bare))
    48  	t.Run("AutoResolveNotWorking", CascadeAutoResolveNotWorking(bare))
    49  	t.Run("MergeToDevelop", MergeToDevelop(bare))
    50  	t.Run("MergeDevelopToDevelop", MergeDevelopToDevelop(bare))
    51  }
    52  
    53  func CascadeNoConflict(bare *git.Repository) func(t *testing.T) {
    54  	return func(t *testing.T) {
    55  		err := WorkOnBareRepository(bare, &CreateDummyFileOnBranchTask{
    56  			BranchName: "release/48",
    57  			Filename:   "patch-1",
    58  			t:          t,
    59  		})
    60  		CheckFatal(err, t)
    61  
    62  		work, err := git.Clone(bare.Path(), filepath.Join(filepath.Dir(bare.Path()), "cascade"), &git.CloneOptions{})
    63  		CheckFatal(err, t)
    64  		defer os.RemoveAll(work.Workdir())
    65  		defer work.Free()
    66  
    67  		client := &Client{
    68  			Repository: work,
    69  			Author: &Author{
    70  				Name:  "Jon Snow",
    71  				Email: "jon.snow@winterfell.net",
    72  			},
    73  		}
    74  
    75  		// assume someone else push a new commit to the same branch
    76  		err = WorkOnBareRepository(bare, &CreateDummyFileOnBranchTask{
    77  			BranchName: "release/48",
    78  			Filename:   "patch-2",
    79  			t:          t,
    80  		})
    81  		CheckFatal(err, t)
    82  
    83  		err = client.CascadeMerge("release/48", nil)
    84  
    85  		stat, err := os.Stat(filepath.Join(work.Workdir(), "patch-1"))
    86  		CheckFatal(err, t)
    87  		if !stat.Mode().IsRegular() {
    88  			t.Fail()
    89  		}
    90  
    91  		stat, err = os.Stat(filepath.Join(work.Workdir(), "patch-2"))
    92  		CheckFatal(err, t)
    93  		if !stat.Mode().IsRegular() {
    94  			t.Fail()
    95  		}
    96  
    97  		CheckFatal(err, t)
    98  	}
    99  }
   100  
   101  func CascadeConflict(bare *git.Repository) func(t *testing.T) {
   102  	return func(t *testing.T) {
   103  		err := WorkOnBareRepository(bare,
   104  			&ChangeFileOnBranchTask{
   105  				BranchName: "release/48",
   106  				Filename:   "foo",
   107  				Content:    "foo-edit-48",
   108  				t:          t,
   109  			},
   110  			&ChangeFileOnBranchTask{
   111  				BranchName: "develop",
   112  				Filename:   "foo",
   113  				Content:    "foo-edit-develop",
   114  				t:          t,
   115  			},
   116  		)
   117  		CheckFatal(err, t)
   118  
   119  		client, err := NewClient(&ClientOptions{
   120  			Path: filepath.Join(filepath.Dir(bare.Path()), "cascade"),
   121  			URL:  bare.Path(),
   122  			Author: &Author{
   123  				Name:  "Jon Snow",
   124  				Email: "jon.snow@winterfell.net",
   125  			},
   126  		})
   127  		CheckFatal(err, t)
   128  		defer os.RemoveAll(filepath.Join(filepath.Dir(bare.Path()), "cascade"))
   129  		defer client.Close()
   130  
   131  		err = client.CascadeMerge("release/48", nil)
   132  		if err == nil {
   133  			t.Fail()
   134  		}
   135  
   136  		err = client.Checkout("release/49")
   137  		CheckFatal(err, t)
   138  		bytes49, err := client.ReadFile("foo")
   139  		CheckFatal(err, t)
   140  
   141  		if !reflect.DeepEqual(bytes49, []byte("foo-edit-48")) {
   142  			t.Fail()
   143  		}
   144  
   145  		err = client.Checkout("develop")
   146  		CheckFatal(err, t)
   147  		bytesDevelop, err := client.ReadFile("foo")
   148  		CheckFatal(err, t)
   149  
   150  		if !reflect.DeepEqual(bytesDevelop, []byte("foo-edit-develop")) {
   151  			t.Fail()
   152  		}
   153  	}
   154  }
   155  
   156  func CascadeAutoResolveNotWorking(bare *git.Repository) func(t *testing.T) {
   157  	return func(t *testing.T) {
   158  		err := WorkOnBareRepository(bare,
   159  			&ChangeFileOnBranchTask{
   160  				BranchName: "release/48",
   161  				Filename:   "foo",
   162  				Content:    "foo-same-edit",
   163  				t:          t,
   164  			},
   165  			&ChangeFileOnBranchTask{
   166  				BranchName: "release/49",
   167  				Filename:   "foo",
   168  				Content:    "foo-same-edit",
   169  				t:          t,
   170  			},
   171  		)
   172  		CheckFatal(err, t)
   173  
   174  		client, err := NewClient(&ClientOptions{
   175  			Path: filepath.Join(filepath.Dir(bare.Path()), "cascade"),
   176  			URL:  bare.Path(),
   177  			Author: &Author{
   178  				Name:  "Jon Snow",
   179  				Email: "jon.snow@winterfell.net",
   180  			},
   181  		})
   182  		CheckFatal(err, t)
   183  		defer os.RemoveAll(filepath.Join(filepath.Dir(bare.Path()), "cascade"))
   184  		defer client.Close()
   185  
   186  		err = client.CascadeMerge("release/48", nil)
   187  		if err == nil {
   188  			t.Fail()
   189  		}
   190  
   191  		err = client.Checkout("release/49")
   192  		CheckFatal(err, t)
   193  		bytes49, err := client.ReadFile("foo")
   194  		CheckFatal(err, t)
   195  
   196  		if !reflect.DeepEqual(bytes49, []byte("foo-same-edit")) {
   197  			t.Fail()
   198  		}
   199  
   200  		err = client.Checkout("develop")
   201  		CheckFatal(err, t)
   202  		bytesDevelop, err := client.ReadFile("foo")
   203  		CheckFatal(err, t)
   204  
   205  		if !reflect.DeepEqual(bytesDevelop, []byte("foo-edit-develop")) {
   206  			t.Fail()
   207  		}
   208  	}
   209  }
   210  
   211  func MergeToDevelop(bare *git.Repository) func(t *testing.T) {
   212  	return func(t *testing.T) {
   213  		err := WorkOnBareRepository(bare,
   214  			&CreateDummyFileOnBranchTask{
   215  				BranchName: "release/49",
   216  				Filename:   "abc",
   217  				t:          t,
   218  			})
   219  		CheckFatal(err, t)
   220  
   221  		path := filepath.Join(filepath.Dir(bare.Path()), "cascade")
   222  		client, err := NewClient(&ClientOptions{
   223  			Path: path,
   224  			URL:  bare.Path(),
   225  			Author: &Author{
   226  				Name:  "Jon Snow",
   227  				Email: "jon.snow@winterfell.net",
   228  			},
   229  		})
   230  		CheckFatal(err, t)
   231  		defer os.RemoveAll(path)
   232  		defer client.Close()
   233  
   234  		err = client.CascadeMerge("release/49", nil)
   235  
   236  		err = WorkOnBareRepository(bare,
   237  			&FileExistsOnBranchTask{
   238  				BranchName: "release/49",
   239  				Filename:   "abc",
   240  				t:          t,
   241  			})
   242  		CheckFatal(err, t)
   243  
   244  	}
   245  }
   246  
   247  func MergeDevelopToDevelop(bare *git.Repository) func(t *testing.T) {
   248  	return func(t *testing.T) {
   249  		err := WorkOnBareRepository(bare,
   250  			&CreateDummyFileOnBranchTask{
   251  				BranchName: "develop",
   252  				Filename:   "def",
   253  				t:          t,
   254  			})
   255  		CheckFatal(err, t)
   256  
   257  		path := filepath.Join(filepath.Dir(bare.Path()), "cascade")
   258  		work, err := git.Clone(bare.Path(), path, &git.CloneOptions{})
   259  		CheckFatal(err, t)
   260  		CheckFatal(err, t)
   261  		defer os.RemoveAll(work.Workdir())
   262  		defer work.Free()
   263  
   264  		client := &Client{
   265  			Repository: work,
   266  			Author: &Author{
   267  				Name:  "Jon Snow",
   268  				Email: "jon.snow@winterfell.net",
   269  			},
   270  		}
   271  
   272  		CheckFatal(err, t)
   273  
   274  		client.Checkout("develop")
   275  		head, err := work.Head()
   276  		defer head.Free()
   277  		CheckFatal(err, t)
   278  
   279  		// should do nothing
   280  		err = client.CascadeMerge("develop", nil)
   281  		actual, err := work.Head()
   282  
   283  		CheckFatal(err, t)
   284  		if actual.Target().String() != head.Target().String() {
   285  			t.Fail()
   286  		}
   287  
   288  		CheckFatal(err, t)
   289  	}
   290  }
   291  
   292  func CheckFatal(err error, t *testing.T) {
   293  	if err != nil {
   294  		t.Fatal(err)
   295  	}
   296  }
   297  
   298  func (c *Client) CommitDummyFile(filename string, t *testing.T) {
   299  	c.NewFile(filename, filename+"\n", t)
   300  	_, err := c.Commit("add "+filename, filename)
   301  	CheckFatal(err, t)
   302  }
   303  
   304  func (c *Client) NewFile(filename string, content string, t *testing.T) {
   305  	err := c.WriteFile(filename, []byte(content), 0644)
   306  	if err != nil {
   307  		CheckFatal(err, t)
   308  	}
   309  }
   310  
   311  func (c *Client) ReadFile(filename string) ([]byte, error) {
   312  	return ioutil.ReadFile(filepath.Join(c.Repository.Workdir(), filename))
   313  }
   314  
   315  func (c *Client) WriteFile(filename string, data []byte, perm os.FileMode) error {
   316  	return ioutil.WriteFile(filepath.Join(c.Repository.Workdir(), filename), data, perm)
   317  }
   318  
   319  type Task interface {
   320  	Do(client *Client)
   321  }
   322  
   323  type InitializeWithReadmeTask struct {
   324  	t *testing.T
   325  }
   326  
   327  func (t *InitializeWithReadmeTask) Do(client *Client) {
   328  	filename := "README.md"
   329  	client.NewFile(filename, "# Cascade Merge\n", t.t)
   330  
   331  	_, err := client.Commit("initial commit", filename)
   332  	CheckFatal(err, t.t)
   333  	err = client.Push("master")
   334  	CheckFatal(err, t.t)
   335  }
   336  
   337  type CreateDummyFileOnBranchTask struct {
   338  	BranchName string
   339  	Filename   string
   340  	t          *testing.T
   341  }
   342  
   343  func (t *CreateDummyFileOnBranchTask) Do(client *Client) {
   344  	err := client.Checkout(t.BranchName)
   345  	CheckFatal(err, t.t)
   346  	client.CommitDummyFile(t.Filename, t.t)
   347  	err = client.Push(t.BranchName)
   348  	CheckFatal(err, t.t)
   349  }
   350  
   351  type ChangeFileOnBranchTask struct {
   352  	BranchName string
   353  	Filename   string
   354  	Content    string
   355  	t          *testing.T
   356  }
   357  
   358  func (t *ChangeFileOnBranchTask) Do(client *Client) {
   359  	err := client.Checkout(t.BranchName)
   360  	CheckFatal(err, t.t)
   361  
   362  	err = ioutil.WriteFile(t.Filename, []byte(t.Content), 0644)
   363  	CheckFatal(err, t.t)
   364  
   365  	_, err = client.Commit("edit "+t.Filename, t.Filename)
   366  	CheckFatal(err, t.t)
   367  
   368  	err = client.Push(t.BranchName)
   369  	CheckFatal(err, t.t)
   370  }
   371  
   372  type FileExistsOnBranchTask struct {
   373  	BranchName string
   374  	Filename   string
   375  	t          *testing.T
   376  }
   377  
   378  func (t *FileExistsOnBranchTask) Do(client *Client) {
   379  	err := client.Checkout(t.BranchName)
   380  	CheckFatal(err, t.t)
   381  
   382  	stat, err := os.Stat(filepath.Join(client.Repository.Workdir(), t.Filename))
   383  	CheckFatal(err, t.t)
   384  
   385  	if !stat.Mode().IsRegular() {
   386  		t.t.Fatal(t.Filename + " does not exist on " + t.BranchName)
   387  	}
   388  }
   389  
   390  func WorkOnBareRepository(bare *git.Repository, tasks ...Task) error {
   391  
   392  	cwd, err := os.Getwd()
   393  	if err != nil {
   394  		return err
   395  	}
   396  
   397  	tmp, err := ioutil.TempDir(os.TempDir(), "cascade-test-")
   398  	if err != nil {
   399  		return err
   400  	}
   401  	defer os.RemoveAll(tmp)
   402  
   403  	client, err := NewClient(&ClientOptions{
   404  		Path: tmp,
   405  		URL:  bare.Path(),
   406  		Author: &Author{
   407  			Name:  "Jon Snow",
   408  			Email: "jon.snow@winterfell.net",
   409  		},
   410  	})
   411  	if err != nil {
   412  		return err
   413  	}
   414  	defer client.Close()
   415  
   416  	err = os.Chdir(tmp)
   417  	if err != nil {
   418  		return err
   419  	}
   420  	defer os.Chdir(cwd)
   421  
   422  	for _, t := range tasks {
   423  		t.Do(client)
   424  	}
   425  
   426  	return nil
   427  }
   428  
   429  func TestClientOptions_Validate(t *testing.T) {
   430  	type fields struct {
   431  		Author *Author
   432  		Path   string
   433  		URL    string
   434  	}
   435  	tests := []struct {
   436  		name   string
   437  		fields fields
   438  		want   bool
   439  	}{
   440  		{
   441  			name: "Valid",
   442  			fields: fields{
   443  				Author: &Author{Name: "Jon Snow", Email: "jon@snow.nl"},
   444  				Path:   "907ab3da-653e-460e-bb5b-11b0b0b95e3f",
   445  				URL:    "https://git.com/winterfell.git",
   446  			},
   447  			want: true,
   448  		}, {
   449  			name: "Invalid",
   450  			fields: fields{
   451  				Author: &Author{Name: "Jon Snow", Email: "jon@snow.nl"},
   452  				Path:   "907ab3da-653e-460e-bb5b-11b0b0b95e3f",
   453  				URL:    "",
   454  			},
   455  			want: false,
   456  		},
   457  	}
   458  	for _, tt := range tests {
   459  		t.Run(tt.name, func(t *testing.T) {
   460  			o := &ClientOptions{
   461  				Author: tt.fields.Author,
   462  				Path:   tt.fields.Path,
   463  				URL:    tt.fields.URL,
   464  			}
   465  			if got := o.Validate(); got != tt.want {
   466  				t.Errorf("Validate() = %v, want %v", got, tt.want)
   467  			}
   468  		})
   469  	}
   470  }