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 }