1
1
using System ;
2
+ using System . Collections . Generic ;
2
3
using System . IO ;
3
4
using System . Linq ;
4
5
using GitVersion . Extensions ;
@@ -208,9 +209,9 @@ private void NormalizeGitDirectory(string gitDirectory, bool noFetch, string cur
208
209
209
210
try
210
211
{
211
- var remote = repository . EnsureOnlyOneRemoteIsDefined ( log ) ;
212
+ var remote = EnsureOnlyOneRemoteIsDefined ( repository , log ) ;
212
213
213
- repository . AddMissingRefSpecs ( log , remote ) ;
214
+ AddMissingRefSpecs ( repository , log , remote ) ;
214
215
215
216
//If noFetch is enabled, then GitVersion will assume that the git repository is normalized before execution, so that fetching from remotes is not required.
216
217
if ( noFetch )
@@ -223,8 +224,8 @@ private void NormalizeGitDirectory(string gitDirectory, bool noFetch, string cur
223
224
repository . Commands . Fetch ( remote . Name , new string [ 0 ] , authentication . ToFetchOptions ( ) , null ) ;
224
225
}
225
226
226
- repository . EnsureLocalBranchExistsForCurrentBranch ( log , remote , currentBranch ) ;
227
- repository . CreateOrUpdateLocalBranchesFromRemoteTrackingOnes ( log , remote . Name ) ;
227
+ EnsureLocalBranchExistsForCurrentBranch ( repository , log , remote , currentBranch ) ;
228
+ CreateOrUpdateLocalBranchesFromRemoteTrackingOnes ( repository , log , remote . Name ) ;
228
229
229
230
// Bug fix for https://github.com/GitTools/GitVersion/issues/1754, head maybe have been changed
230
231
// if this is a dynamic repository. But only allow this in case the branches are different (branch switch)
@@ -294,7 +295,7 @@ private void NormalizeGitDirectory(string gitDirectory, bool noFetch, string cur
294
295
else if ( localBranchesWhereCommitShaIsHead . Count == 0 )
295
296
{
296
297
log . Info ( $ "No local branch pointing at the commit '{ headSha } '. Fake branch needs to be created.") ;
297
- repository . CreateFakeBranchPointingAtThePullRequestTip ( log , authentication ) ;
298
+ CreateFakeBranchPointingAtThePullRequestTip ( repository , log , authentication ) ;
298
299
}
299
300
else
300
301
{
@@ -318,5 +319,194 @@ To disable this error set an environmental variable called IGNORE_NORMALISATION_
318
319
}
319
320
}
320
321
}
322
+
323
+ private static void CreateOrUpdateLocalBranchesFromRemoteTrackingOnes ( IGitRepository repo , ILog log , string remoteName )
324
+ {
325
+ var prefix = $ "refs/remotes/{ remoteName } /";
326
+ var remoteHeadCanonicalName = $ "{ prefix } HEAD";
327
+ var remoteTrackingReferences = repo . Refs
328
+ . FromGlob ( prefix + "*" )
329
+ . Where ( r => ! r . CanonicalName . IsEquivalentTo ( remoteHeadCanonicalName ) ) ;
330
+
331
+ foreach ( var remoteTrackingReference in remoteTrackingReferences )
332
+ {
333
+ var remoteTrackingReferenceName = remoteTrackingReference . CanonicalName ;
334
+ var branchName = remoteTrackingReferenceName . Substring ( prefix . Length ) ;
335
+ var localCanonicalName = "refs/heads/" + branchName ;
336
+
337
+ // We do not want to touch our current branch
338
+ if ( branchName . IsEquivalentTo ( repo . Head . FriendlyName ) ) continue ;
339
+
340
+ if ( repo . Refs . Any ( x => x . CanonicalName . IsEquivalentTo ( localCanonicalName ) ) )
341
+ {
342
+ var localRef = repo . Refs [ localCanonicalName ] ;
343
+ var remotedirectReference = remoteTrackingReference . ResolveToDirectReference ( ) ;
344
+ if ( localRef . ResolveToDirectReference ( ) . TargetIdentifier == remotedirectReference . TargetIdentifier )
345
+ {
346
+ log . Info ( $ "Skipping update of '{ remoteTrackingReference . CanonicalName } ' as it already matches the remote ref.") ;
347
+ continue ;
348
+ }
349
+ var remoteRefTipId = remotedirectReference . Target . Id ;
350
+ log . Info ( $ "Updating local ref '{ localRef . CanonicalName } ' to point at { remoteRefTipId } .") ;
351
+ repo . Refs . UpdateTarget ( localRef , remoteRefTipId ) ;
352
+ continue ;
353
+ }
354
+
355
+ log . Info ( $ "Creating local branch from remote tracking '{ remoteTrackingReference . CanonicalName } '.") ;
356
+ repo . Refs . Add ( localCanonicalName , new ObjectId ( remoteTrackingReference . ResolveToDirectReference ( ) . TargetIdentifier ) , true ) ;
357
+
358
+ var branch = repo . Branches [ branchName ] ;
359
+ repo . Branches . Update ( branch , b => b . TrackedBranch = remoteTrackingReferenceName ) ;
360
+ }
361
+ }
362
+
363
+ private static void EnsureLocalBranchExistsForCurrentBranch ( IGitRepository repo , ILog log , Remote remote , string currentBranch )
364
+ {
365
+ if ( log is null )
366
+ {
367
+ throw new ArgumentNullException ( nameof ( log ) ) ;
368
+ }
369
+
370
+ if ( remote is null )
371
+ {
372
+ throw new ArgumentNullException ( nameof ( remote ) ) ;
373
+ }
374
+
375
+ if ( string . IsNullOrEmpty ( currentBranch ) ) return ;
376
+
377
+ var isRef = currentBranch . Contains ( "refs" ) ;
378
+ var isBranch = currentBranch . Contains ( "refs/heads" ) ;
379
+ var localCanonicalName = ! isRef
380
+ ? "refs/heads/" + currentBranch
381
+ : isBranch
382
+ ? currentBranch
383
+ : currentBranch . Replace ( "refs/" , "refs/heads/" ) ;
384
+
385
+ var repoTip = repo . Head . Tip ;
386
+
387
+ // We currently have the rep.Head of the *default* branch, now we need to look up the right one
388
+ var originCanonicalName = $ "{ remote . Name } /{ currentBranch } ";
389
+ var originBranch = repo . Branches [ originCanonicalName ] ;
390
+ if ( originBranch != null )
391
+ {
392
+ repoTip = originBranch . Tip ;
393
+ }
394
+
395
+ var repoTipId = repoTip . Id ;
396
+
397
+ if ( repo . Branches . All ( b => ! b . CanonicalName . IsEquivalentTo ( localCanonicalName ) ) )
398
+ {
399
+ log . Info ( isBranch ? $ "Creating local branch { localCanonicalName } "
400
+ : $ "Creating local branch { localCanonicalName } pointing at { repoTipId } ") ;
401
+ repo . Refs . Add ( localCanonicalName , repoTipId ) ;
402
+ }
403
+ else
404
+ {
405
+ log . Info ( isBranch ? $ "Updating local branch { localCanonicalName } to point at { repoTip . Sha } "
406
+ : $ "Updating local branch { localCanonicalName } to match ref { currentBranch } ") ;
407
+ var localRef = repo . Refs [ localCanonicalName ] ;
408
+ repo . Refs . UpdateTarget ( localRef , repoTipId ) ;
409
+ }
410
+
411
+ repo . Commands . Checkout ( localCanonicalName ) ;
412
+ }
413
+
414
+ private static Remote EnsureOnlyOneRemoteIsDefined ( IGitRepository repo , ILog log )
415
+ {
416
+ var remotes = repo . Network . Remotes ;
417
+ var howMany = remotes . Count ( ) ;
418
+
419
+ if ( howMany == 1 )
420
+ {
421
+ var remote = remotes . Single ( ) ;
422
+ log . Info ( $ "One remote found ({ remote . Name } -> '{ remote . Url } ').") ;
423
+ return remote ;
424
+ }
425
+
426
+ var message = $ "{ howMany } remote(s) have been detected. When being run on a build server, the Git repository is expected to bear one (and no more than one) remote.";
427
+ throw new WarningException ( message ) ;
428
+ }
429
+
430
+ private static void AddMissingRefSpecs ( IGitRepository repo , ILog log , Remote remote )
431
+ {
432
+ if ( remote . FetchRefSpecs . Any ( r => r . Source == "refs/heads/*" ) )
433
+ return ;
434
+
435
+ var allBranchesFetchRefSpec = $ "+refs/heads/*:refs/remotes/{ remote . Name } /*";
436
+
437
+ log . Info ( $ "Adding refspec: { allBranchesFetchRefSpec } ") ;
438
+
439
+ repo . Network . Remotes . Update ( remote . Name ,
440
+ r => r . FetchRefSpecs . Add ( allBranchesFetchRefSpec ) ) ;
441
+ }
442
+
443
+ private static void CreateFakeBranchPointingAtThePullRequestTip ( IGitRepository repo , ILog log , AuthenticationInfo authentication )
444
+ {
445
+ var remote = repo . Network . Remotes . Single ( ) ;
446
+
447
+ log . Info ( "Fetching remote refs to see if there is a pull request ref" ) ;
448
+ var remoteTips = ( string . IsNullOrEmpty ( authentication . Username ) ?
449
+ GetRemoteTipsForAnonymousUser ( repo , remote ) :
450
+ GetRemoteTipsUsingUsernamePasswordCredentials ( repo , remote , authentication . Username , authentication . Password ) )
451
+ . ToList ( ) ;
452
+
453
+ log . Info ( $ "Remote Refs:{ System . Environment . NewLine } " + string . Join ( System . Environment . NewLine , remoteTips . Select ( r => r . CanonicalName ) ) ) ;
454
+
455
+ var headTipSha = repo . Head . Tip . Sha ;
456
+
457
+ var refs = remoteTips . Where ( r => r . TargetIdentifier == headTipSha ) . ToList ( ) ;
458
+
459
+ if ( refs . Count == 0 )
460
+ {
461
+ var message = $ "Couldn't find any remote tips from remote '{ remote . Url } ' pointing at the commit '{ headTipSha } '.";
462
+ throw new WarningException ( message ) ;
463
+ }
464
+
465
+ if ( refs . Count > 1 )
466
+ {
467
+ var names = string . Join ( ", " , refs . Select ( r => r . CanonicalName ) ) ;
468
+ var message = $ "Found more than one remote tip from remote '{ remote . Url } ' pointing at the commit '{ headTipSha } '. Unable to determine which one to use ({ names } ).";
469
+ throw new WarningException ( message ) ;
470
+ }
471
+
472
+ var reference = refs [ 0 ] ;
473
+ var canonicalName = reference . CanonicalName ;
474
+ log . Info ( $ "Found remote tip '{ canonicalName } ' pointing at the commit '{ headTipSha } '.") ;
475
+
476
+ if ( canonicalName . StartsWith ( "refs/tags" ) )
477
+ {
478
+ log . Info ( $ "Checking out tag '{ canonicalName } '") ;
479
+ repo . Commands . Checkout ( reference . Target . Sha ) ;
480
+ return ;
481
+ }
482
+
483
+ if ( ! canonicalName . StartsWith ( "refs/pull/" ) && ! canonicalName . StartsWith ( "refs/pull-requests/" ) )
484
+ {
485
+ var message = $ "Remote tip '{ canonicalName } ' from remote '{ remote . Url } ' doesn't look like a valid pull request.";
486
+ throw new WarningException ( message ) ;
487
+ }
488
+
489
+ var fakeBranchName = canonicalName . Replace ( "refs/pull/" , "refs/heads/pull/" ) . Replace ( "refs/pull-requests/" , "refs/heads/pull-requests/" ) ;
490
+
491
+ log . Info ( $ "Creating fake local branch '{ fakeBranchName } '.") ;
492
+ repo . Refs . Add ( fakeBranchName , new ObjectId ( headTipSha ) ) ;
493
+
494
+ log . Info ( $ "Checking local branch '{ fakeBranchName } ' out.") ;
495
+ repo . Commands . Checkout ( fakeBranchName ) ;
496
+ }
497
+
498
+ private static IEnumerable < DirectReference > GetRemoteTipsUsingUsernamePasswordCredentials ( IGitRepository repository , Remote remote , string username , string password )
499
+ {
500
+ return repository . Network . ListReferences ( remote , ( url , fromUrl , types ) => new UsernamePasswordCredentials
501
+ {
502
+ Username = username ,
503
+ Password = password ?? string . Empty
504
+ } ) . Select ( r => r . ResolveToDirectReference ( ) ) ;
505
+ }
506
+
507
+ private static IEnumerable < DirectReference > GetRemoteTipsForAnonymousUser ( IGitRepository repository , Remote remote )
508
+ {
509
+ return repository . Network . ListReferences ( remote ) . Select ( r => r . ResolveToDirectReference ( ) ) ;
510
+ }
321
511
}
322
512
}
0 commit comments