1717using GitUIPluginInterfaces ;
1818using GitExtensions . Extensibility . Plugins ;
1919using GitExtensions . Extensibility . Settings ;
20- using Newtonsoft . Json . Linq ;
20+ using System . Text . Json ;
21+ using System . Text . Json . Nodes ;
2122using ResourceManager ;
2223using GitExtensions . Extensibility . Git ;
2324using GitExtensions . Extensibility . Settings . UserControls ;
@@ -85,8 +86,8 @@ public class Plugin : GitPluginBase, IGitPluginForRepository, IGitPluginForCommi
8586 private readonly BoolSetting _enabledSettings = new BoolSetting ( "Enabled" , false ) ;
8687 private readonly StringSetting _projectUrlSettings = new StringSetting ( "AzureDevOps project URL" , @"https://dev.azure.com/{organization}/{project}" ) ;
8788 private readonly CredentialsSetting _credentialsSettings ;
88- private readonly StringSetting _wiqlQuerySettings = new StringSetting ( "WIQL Query" , AzureDevOpsQueryLabel . Text , "Select [System.Id] From WorkItems Where ( [System.WorkItemType] = 'Product Backlog Item' OR [System.WorkItemType] = 'Bug' ) AND [State] <> 'Closed' AND [State] <> 'Removed' AND [System.AssignedTo] = @Me AND [System.IterationPath] = @CurrentIteration order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc" , true ) ;
89- private readonly StringSetting _stringTemplateSetting = new StringSetting ( "AzureDevOps Message Template" , MessageTemplateLabel . Text , DefaultFormat , true ) ;
89+ private readonly StringSetting _wiqlQuerySettings = new StringSetting ( "WIQL Query" , AzureDevOpsQueryLabel . Text , "Select [System.Id] From WorkItems Where ( [System.WorkItemType] = 'Product Backlog Item' OR [System.WorkItemType] = 'Bug' ) AND [State] <> 'Closed' AND [State] <> 'Removed' AND [System.AssignedTo] = @Me AND [System.IterationPath] = @CurrentIteration order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc" ) ;
90+ private readonly StringSetting _stringTemplateSetting = new StringSetting ( "AzureDevOps Message Template" , MessageTemplateLabel . Text , DefaultFormat ) ;
9091 private readonly PseudoSetting _allFieldsAndValuesSetting = new PseudoSetting ( HowToRetrieveWorkItemsFieldsValues . Text , WorkItemsFieldsValuesCaption . Text , DpiUtil . Scale ( 200 ) , t => t . ScrollBars = ScrollBars . Both ) ;
9192
9293 private IGitModule _gitModule ;
@@ -397,16 +398,23 @@ private async Task<IList<CommitTemplate>> GetCommitTemplatesAsync(HttpClient cli
397398 return new [ ] { new CommitTemplate ( $ "{ Description } { Error } ", ex . ToString ( ) ) } ;
398399 }
399400 }
400-
401401 private IEnumerable < ( string id , string url ) > GetWorkitems ( string jsonWorkitems )
402402 {
403- var workItems = JObject . Parse ( jsonWorkitems ) [ "workItems" ] ;
404- if ( workItems == null )
403+ using var doc = JsonDocument . Parse ( jsonWorkitems ) ;
404+ if ( ! doc . RootElement . TryGetProperty ( " workItems" , out var workItemsElement ) || workItemsElement . ValueKind != JsonValueKind . Array )
405405 {
406406 return Enumerable . Empty < ( string id , string url ) > ( ) ;
407407 }
408408
409- return workItems . Select ( w => ( w [ "id" ] . ToString ( ) , w [ "url" ] . ToString ( ) ) ) ;
409+ var result = new List < ( string id , string url ) > ( ) ;
410+ foreach ( var item in workItemsElement . EnumerateArray ( ) )
411+ {
412+ var id = item . TryGetProperty ( "id" , out var idEl ) ? ( idEl . ValueKind == JsonValueKind . String ? idEl . GetString ( ) : idEl . GetRawText ( ) ) : string . Empty ;
413+ var url = item . TryGetProperty ( "url" , out var urlEl ) ? urlEl . GetString ( ) ?? urlEl . GetRawText ( ) : string . Empty ;
414+ result . Add ( ( id ?? string . Empty , url ?? string . Empty ) ) ;
415+ }
416+
417+ return result ;
410418 }
411419
412420 private async Task < CommitTemplate > GetWorkItemAsync ( HttpClient client , string id , string url , string template )
@@ -433,7 +441,12 @@ private async Task<CommitTemplate> GetWorkItemAsync(HttpClient client, string id
433441
434442 private CommitTemplate GetCommitTemplateFromWorkitemData ( string id , string template , string jsonWorkitem )
435443 {
436- var workItemData = JObject . Parse ( jsonWorkitem ) [ "fields" ] ;
444+ using var doc = JsonDocument . Parse ( jsonWorkitem ) ;
445+ if ( ! doc . RootElement . TryGetProperty ( "fields" , out var workItemData ) )
446+ {
447+ workItemData = default ;
448+ }
449+
437450 if ( _btnPreview != null )
438451 {
439452 _allFieldsAndValues = ExtractAllFields ( workItemData ) ;
@@ -459,81 +472,96 @@ private string Elide(string value)
459472 return value . Length > 80 ? value . Substring ( 0 , 80 ) + "[...]" : value ;
460473 }
461474
462- private string ExtractAllFields ( JToken fields )
475+ private string ExtractAllFields ( JsonElement fields )
463476 {
464- List < string > allFields = new List < string > ( ) ;
465- foreach ( var pair in ( JObject ) fields )
477+ var allFields = new List < string > ( ) ;
478+ if ( fields . ValueKind != JsonValueKind . Object )
466479 {
467- if ( ! pair . Value . HasValues )
480+ return string . Empty ;
481+ }
482+
483+ foreach ( var prop in fields . EnumerateObject ( ) )
484+ {
485+ if ( prop . Value . ValueKind != JsonValueKind . Object )
468486 {
469- allFields . Add ( $ "{{{pair.Key }}}: { Elide ( pair . Value . ToString ( ) ) } ") ;
487+ allFields . Add ( $ "{{{prop.Name }}}: { Elide ( ElementToString ( prop . Value ) ) } ") ;
470488 }
471489 else
472490 {
473- allFields . AddRange ( FlattenFieldsHierarchy ( pair . Key , pair . Value ) ) ;
491+ allFields . AddRange ( FlattenFieldsHierarchy ( prop . Name , prop . Value ) ) ;
474492 }
475493 }
476494
477495 return string . Join ( Environment . NewLine , allFields . OrderBy ( s => s ) ) ;
478496 }
479497
480- private IEnumerable < string > FlattenFieldsHierarchy ( string key , JToken fields )
498+ private IEnumerable < string > FlattenFieldsHierarchy ( string key , JsonElement fields )
481499 {
482- foreach ( var pair in ( JObject ) fields )
500+ if ( fields . ValueKind != JsonValueKind . Object )
501+ {
502+ yield break ;
503+ }
504+
505+ foreach ( var prop in fields . EnumerateObject ( ) )
483506 {
484- if ( ! pair . Value . HasValues )
507+ if ( prop . Value . ValueKind != JsonValueKind . Object )
485508 {
486- yield return $ "{{{key}|{ pair . Key } }}: { Elide ( pair . Value . ToString ( ) ) } ";
509+ yield return $ "{{{key}|{ prop . Name } }}: { Elide ( ElementToString ( prop . Value ) ) } ";
487510 }
488511 else
489512 {
490- foreach ( string value in FlattenFieldsHierarchy ( key + "|" + pair . Key , pair . Value ) )
513+ foreach ( var value in FlattenFieldsHierarchy ( key + "|" + prop . Name , prop . Value ) )
491514 {
492515 yield return value ;
493516 }
494517 }
495518 }
496519 }
497520
498- private string PopulateTemplate ( string titleTemplate , string id , JToken workitem )
521+ private string PopulateTemplate ( string titleTemplate , string id , JsonElement workitem )
499522 {
500523 return ExtractWorkItemField ( titleTemplate . Replace ( "{id}" , id ) , workitem ) ;
501524 }
502525
503- private static bool TryGetWorkItemValue ( JToken workitem , string workItemField , out string value )
526+ private static bool TryGetWorkItemValue ( JsonElement workitem , string workItemField , out string value )
504527 {
528+ value = null ;
505529 if ( workItemField . Contains ( '|' ) )
506530 {
507531 var fields = workItemField . Split ( '|' ) ;
532+ var current = workitem ;
508533 foreach ( var field in fields )
509534 {
510- var foundValue = workitem [ field ] ;
511- if ( foundValue == null )
535+ if ( current . ValueKind != JsonValueKind . Object || ! current . TryGetProperty ( field , out var foundValue ) )
512536 {
513537 value = null ;
514538 return false ;
515539 }
516540
517- workitem = foundValue ;
541+ current = foundValue ;
518542 }
519543
520- value = workitem . ToString ( ) ;
544+ value = ElementToString ( current ) ;
521545 return true ;
522546 }
523547 else
524548 {
525- var foundValue = workitem [ workItemField ] ;
526- value = foundValue ? . ToString ( ) ;
527- if ( workItemField == "System.Description" )
549+ if ( workitem . ValueKind == JsonValueKind . Object && workitem . TryGetProperty ( workItemField , out var foundValue ) )
528550 {
529- value = ExtractTextFromHtml ( value ) ;
551+ value = ElementToString ( foundValue ) ;
552+ if ( workItemField == "System.Description" )
553+ {
554+ value = ExtractTextFromHtml ( value ) ;
555+ }
556+
557+ return value != null ;
530558 }
531559
532- return value != null ;
560+ return false ;
533561 }
534562 }
535563
536- public static string ExtractWorkItemField ( string pattern , JToken workitem )
564+ public static string ExtractWorkItemField ( string pattern , JsonElement workitem )
537565 {
538566 if ( string . IsNullOrWhiteSpace ( pattern ) )
539567 {
@@ -557,6 +585,21 @@ public static string ExtractWorkItemField(string pattern, JToken workitem)
557585 return pattern ;
558586 }
559587
588+ private static string ElementToString ( JsonElement el )
589+ {
590+ if ( el . ValueKind == JsonValueKind . String )
591+ {
592+ return el . GetString ( ) ;
593+ }
594+
595+ if ( el . ValueKind == JsonValueKind . Null || el . ValueKind == JsonValueKind . Undefined )
596+ {
597+ return null ;
598+ }
599+
600+ return el . GetRawText ( ) ;
601+ }
602+
560603 public static string ExtractTextFromHtml ( string html )
561604 {
562605 if ( string . IsNullOrWhiteSpace ( html ) )
0 commit comments