|
1 | 1 | using System;
|
| 2 | +using System.Collections.Generic; |
2 | 3 | using System.IO;
|
3 | 4 | using System.Linq;
|
| 5 | +using LibGit2Sharp.Handlers; |
4 | 6 | using LibGit2Sharp.Tests.TestHelpers;
|
5 | 7 | using Xunit;
|
6 | 8 | using Xunit.Extensions;
|
@@ -242,5 +244,243 @@ public void CloningWithoutUrlThrows()
|
242 | 244 |
|
243 | 245 | Assert.Throws<ArgumentNullException>(() => Repository.Clone(null, scd.DirectoryPath));
|
244 | 246 | }
|
| 247 | + |
| 248 | + /// <summary> |
| 249 | + /// Private helper to record the callbacks that were called as part of a clone. |
| 250 | + /// </summary> |
| 251 | + private class CloneCallbackInfo |
| 252 | + { |
| 253 | + /// <summary> |
| 254 | + /// Was checkout progress called. |
| 255 | + /// </summary> |
| 256 | + public bool CheckoutProgressCalled { get; set; } |
| 257 | + |
| 258 | + /// <summary> |
| 259 | + /// The reported remote URL. |
| 260 | + /// </summary> |
| 261 | + public string RemoteUrl { get; set; } |
| 262 | + |
| 263 | + /// <summary> |
| 264 | + /// Was remote ref update called. |
| 265 | + /// </summary> |
| 266 | + public bool RemoteRefUpdateCalled { get; set; } |
| 267 | + |
| 268 | + /// <summary> |
| 269 | + /// Was the transition callback called when starting |
| 270 | + /// work on this repository. |
| 271 | + /// </summary> |
| 272 | + public bool StartingWorkInRepositoryCalled { get; set; } |
| 273 | + |
| 274 | + /// <summary> |
| 275 | + /// Was the transition callback called when finishing |
| 276 | + /// work on this repository. |
| 277 | + /// </summary> |
| 278 | + public bool FinishedWorkInRepositoryCalled { get; set; } |
| 279 | + |
| 280 | + /// <summary> |
| 281 | + /// The reported recursion depth. |
| 282 | + /// </summary> |
| 283 | + public int RecursionDepth { get; set; } |
| 284 | + } |
| 285 | + |
| 286 | + [Fact] |
| 287 | + public void CanRecursivelyCloneSubmodules() |
| 288 | + { |
| 289 | + var uri = new Uri(Path.GetFullPath(SandboxSubmoduleSmallTestRepo())); |
| 290 | + var scd = BuildSelfCleaningDirectory(); |
| 291 | + string relativeSubmodulePath = "submodule_target_wd"; |
| 292 | + |
| 293 | + // Construct the expected URL the submodule will clone from. |
| 294 | + string expectedSubmoduleUrl = Path.Combine(Path.GetDirectoryName(uri.AbsolutePath), relativeSubmodulePath); |
| 295 | + expectedSubmoduleUrl = expectedSubmoduleUrl.Replace('\\', '/'); |
| 296 | + |
| 297 | + Dictionary<string, CloneCallbackInfo> callbacks = new Dictionary<string, CloneCallbackInfo>(); |
| 298 | + |
| 299 | + CloneCallbackInfo currentEntry = null; |
| 300 | + bool unexpectedOrderOfCallbacks = false; |
| 301 | + |
| 302 | + CheckoutProgressHandler checkoutProgressHandler = (x, y, z) => |
| 303 | + { |
| 304 | + if (currentEntry != null) |
| 305 | + { |
| 306 | + currentEntry.CheckoutProgressCalled = true; |
| 307 | + } |
| 308 | + else |
| 309 | + { |
| 310 | + // Should not be called if there is not a current |
| 311 | + // callbackInfo entry. |
| 312 | + unexpectedOrderOfCallbacks = true; |
| 313 | + } |
| 314 | + }; |
| 315 | + |
| 316 | + UpdateTipsHandler remoteRefUpdated = (x, y, z) => |
| 317 | + { |
| 318 | + if (currentEntry != null) |
| 319 | + { |
| 320 | + currentEntry.RemoteRefUpdateCalled = true; |
| 321 | + } |
| 322 | + else |
| 323 | + { |
| 324 | + // Should not be called if there is not a current |
| 325 | + // callbackInfo entry. |
| 326 | + unexpectedOrderOfCallbacks = true; |
| 327 | + } |
| 328 | + |
| 329 | + return true; |
| 330 | + }; |
| 331 | + |
| 332 | + RepositoryOperationStarting repositoryOperationStarting = (x) => |
| 333 | + { |
| 334 | + if (currentEntry != null) |
| 335 | + { |
| 336 | + // Should not be called if there is a current |
| 337 | + // callbackInfo entry. |
| 338 | + unexpectedOrderOfCallbacks = true; |
| 339 | + } |
| 340 | + |
| 341 | + currentEntry = new CloneCallbackInfo(); |
| 342 | + currentEntry.StartingWorkInRepositoryCalled = true; |
| 343 | + currentEntry.RecursionDepth = x.RecursionDepth; |
| 344 | + currentEntry.RemoteUrl = x.RemoteUrl; |
| 345 | + callbacks.Add(x.RepositoryPath, currentEntry); |
| 346 | + |
| 347 | + return true; |
| 348 | + }; |
| 349 | + |
| 350 | + RepositoryOperationCompleted repositoryOperationCompleted = (x) => |
| 351 | + { |
| 352 | + if (currentEntry != null) |
| 353 | + { |
| 354 | + currentEntry.FinishedWorkInRepositoryCalled = true; |
| 355 | + currentEntry = null; |
| 356 | + } |
| 357 | + else |
| 358 | + { |
| 359 | + // Should not be called if there is not a current |
| 360 | + // callbackInfo entry. |
| 361 | + unexpectedOrderOfCallbacks = true; |
| 362 | + } |
| 363 | + }; |
| 364 | + |
| 365 | + CloneOptions options = new CloneOptions() |
| 366 | + { |
| 367 | + RecurseSubmodules = true, |
| 368 | + OnCheckoutProgress = checkoutProgressHandler, |
| 369 | + OnUpdateTips = remoteRefUpdated, |
| 370 | + RepositoryOperationStarting = repositoryOperationStarting, |
| 371 | + RepositoryOperationCompleted = repositoryOperationCompleted, |
| 372 | + }; |
| 373 | + |
| 374 | + string clonedRepoPath = Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options); |
| 375 | + string workDirPath; |
| 376 | + |
| 377 | + using(Repository repo = new Repository(clonedRepoPath)) |
| 378 | + { |
| 379 | + workDirPath = repo.Info.WorkingDirectory.TrimEnd(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); |
| 380 | + } |
| 381 | + |
| 382 | + // Verification: |
| 383 | + // Verify that no callbacks were called in an unexpected order. |
| 384 | + Assert.False(unexpectedOrderOfCallbacks); |
| 385 | + |
| 386 | + Dictionary<string, CloneCallbackInfo> expectedCallbackInfo = new Dictionary<string, CloneCallbackInfo>(); |
| 387 | + expectedCallbackInfo.Add(workDirPath, new CloneCallbackInfo() |
| 388 | + { |
| 389 | + RecursionDepth = 0, |
| 390 | + RemoteUrl = uri.AbsolutePath, |
| 391 | + StartingWorkInRepositoryCalled = true, |
| 392 | + FinishedWorkInRepositoryCalled = true, |
| 393 | + CheckoutProgressCalled = true, |
| 394 | + RemoteRefUpdateCalled = true, |
| 395 | + }); |
| 396 | + |
| 397 | + expectedCallbackInfo.Add(Path.Combine(workDirPath, relativeSubmodulePath), new CloneCallbackInfo() |
| 398 | + { |
| 399 | + RecursionDepth = 1, |
| 400 | + RemoteUrl = expectedSubmoduleUrl, |
| 401 | + StartingWorkInRepositoryCalled = true, |
| 402 | + FinishedWorkInRepositoryCalled = true, |
| 403 | + CheckoutProgressCalled = true, |
| 404 | + RemoteRefUpdateCalled = true, |
| 405 | + }); |
| 406 | + |
| 407 | + // Callbacks for each expected repository that is cloned |
| 408 | + foreach (KeyValuePair<string, CloneCallbackInfo> kvp in expectedCallbackInfo) |
| 409 | + { |
| 410 | + CloneCallbackInfo entry = null; |
| 411 | + Assert.True(callbacks.TryGetValue(kvp.Key, out entry), string.Format("{0} was not found in callbacks.", kvp.Key)); |
| 412 | + |
| 413 | + Assert.Equal(kvp.Value.RemoteUrl, entry.RemoteUrl); |
| 414 | + Assert.Equal(kvp.Value.RecursionDepth, entry.RecursionDepth); |
| 415 | + Assert.Equal(kvp.Value.StartingWorkInRepositoryCalled, entry.StartingWorkInRepositoryCalled); |
| 416 | + Assert.Equal(kvp.Value.FinishedWorkInRepositoryCalled, entry.FinishedWorkInRepositoryCalled); |
| 417 | + Assert.Equal(kvp.Value.CheckoutProgressCalled, entry.CheckoutProgressCalled); |
| 418 | + Assert.Equal(kvp.Value.RemoteRefUpdateCalled, entry.RemoteRefUpdateCalled); |
| 419 | + } |
| 420 | + |
| 421 | + // Verify the state of the submodule |
| 422 | + using(Repository repo = new Repository(clonedRepoPath)) |
| 423 | + { |
| 424 | + var sm = repo.Submodules[relativeSubmodulePath]; |
| 425 | + Assert.True(sm.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir | |
| 426 | + SubmoduleStatus.InConfig | |
| 427 | + SubmoduleStatus.InIndex | |
| 428 | + SubmoduleStatus.InHead)); |
| 429 | + |
| 430 | + Assert.NotNull(sm.HeadCommitId); |
| 431 | + Assert.Equal("480095882d281ed676fe5b863569520e54a7d5c0", sm.HeadCommitId.Sha); |
| 432 | + |
| 433 | + Assert.False(repo.RetrieveStatus().IsDirty); |
| 434 | + } |
| 435 | + } |
| 436 | + |
| 437 | + [Fact] |
| 438 | + public void CanCancelRecursiveClone() |
| 439 | + { |
| 440 | + var uri = new Uri(Path.GetFullPath(SandboxSubmoduleSmallTestRepo())); |
| 441 | + var scd = BuildSelfCleaningDirectory(); |
| 442 | + string relativeSubmodulePath = "submodule_target_wd"; |
| 443 | + |
| 444 | + int cancelDepth = 0; |
| 445 | + |
| 446 | + RepositoryOperationStarting repositoryOperationStarting = (x) => |
| 447 | + { |
| 448 | + return !(x.RecursionDepth >= cancelDepth); |
| 449 | + }; |
| 450 | + |
| 451 | + CloneOptions options = new CloneOptions() |
| 452 | + { |
| 453 | + RecurseSubmodules = true, |
| 454 | + RepositoryOperationStarting = repositoryOperationStarting, |
| 455 | + }; |
| 456 | + |
| 457 | + Assert.Throws<UserCancelledException>(() => |
| 458 | + Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options)); |
| 459 | + |
| 460 | + // Cancel after super repository is cloned, but before submodule is cloned. |
| 461 | + cancelDepth = 1; |
| 462 | + |
| 463 | + string clonedRepoPath = null; |
| 464 | + |
| 465 | + try |
| 466 | + { |
| 467 | + Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options); |
| 468 | + } |
| 469 | + catch(RecurseSubmodulesException ex) |
| 470 | + { |
| 471 | + Assert.NotNull(ex.InnerException); |
| 472 | + Assert.Equal(typeof(UserCancelledException), ex.InnerException.GetType()); |
| 473 | + clonedRepoPath = ex.InitialRepositoryPath; |
| 474 | + } |
| 475 | + |
| 476 | + // Verify that the submodule was not initialized. |
| 477 | + using(Repository repo = new Repository(clonedRepoPath)) |
| 478 | + { |
| 479 | + var submoduleStatus = repo.Submodules[relativeSubmodulePath].RetrieveStatus(); |
| 480 | + Assert.Equal(SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.WorkDirUninitialized, |
| 481 | + submoduleStatus); |
| 482 | + |
| 483 | + } |
| 484 | + } |
245 | 485 | }
|
246 | 486 | }
|
0 commit comments