@@ -565,21 +565,12 @@ impl IssueRepository {
565
565
format ! ( "{}/{}" , self . organization, self . repository)
566
566
}
567
567
568
- async fn has_label ( & self , client : & GithubClient , label : & str ) -> anyhow:: Result < bool > {
569
- #[ allow( clippy:: redundant_pattern_matching) ]
570
- let url = format ! ( "{}/labels/{}" , self . url( client) , label) ;
571
- match client. send_req ( client. get ( & url) ) . await {
572
- Ok ( _) => Ok ( true ) ,
573
- Err ( e) => {
574
- if e. downcast_ref :: < reqwest:: Error > ( )
575
- . map_or ( false , |e| e. status ( ) == Some ( StatusCode :: NOT_FOUND ) )
576
- {
577
- Ok ( false )
578
- } else {
579
- Err ( e)
580
- }
581
- }
582
- }
568
+ async fn labels ( & self , client : & GithubClient ) -> anyhow:: Result < Vec < Label > > {
569
+ let url = format ! ( "{}/labels" , self . url( client) ) ;
570
+ client
571
+ . json ( client. get ( & url) )
572
+ . await
573
+ . context ( "failed to get labels" )
583
574
}
584
575
}
585
576
@@ -730,8 +721,61 @@ impl Issue {
730
721
Ok ( ( ) )
731
722
}
732
723
724
+ async fn normalize_and_match_labels (
725
+ & self ,
726
+ client : & GithubClient ,
727
+ requested_labels : & [ String ] ,
728
+ ) -> anyhow:: Result < Vec < String > > {
729
+ let available_labels = self . repository ( ) . labels ( client) . await . unwrap_or_default ( ) ;
730
+
731
+ let emoji_regex: Regex = Regex :: new ( r"[\p{Emoji}\p{Emoji_Presentation}]" ) . unwrap ( ) ;
732
+ let normalize = |s : & str | emoji_regex. replace_all ( s, "" ) . trim ( ) . to_lowercase ( ) ;
733
+
734
+ let mut found_labels = Vec :: with_capacity ( requested_labels. len ( ) ) ;
735
+ let mut unknown_labels = Vec :: new ( ) ;
736
+
737
+ for requested_label in requested_labels {
738
+ // First look for an exact match
739
+ if let Some ( found) = available_labels. iter ( ) . find ( |l| l. name == * requested_label) {
740
+ found_labels. push ( & found. name ) ;
741
+ continue ;
742
+ }
743
+
744
+ // Try normalizing requested label (remove emoji, case insensitive, trim whitespace)
745
+ let normalized_requested = normalize ( requested_label) ;
746
+ if let Some ( found) = available_labels
747
+ . iter ( )
748
+ . find ( |l| normalize ( & l. name ) == normalized_requested)
749
+ {
750
+ found_labels. push ( & found. name ) ;
751
+ } else {
752
+ unknown_labels. push ( requested_label. as_str ( ) ) ;
753
+ }
754
+ }
755
+
756
+ if !unknown_labels. is_empty ( ) {
757
+ return Err ( UnknownLabels {
758
+ labels : unknown_labels. into_iter ( ) . map ( String :: from) . collect ( ) ,
759
+ }
760
+ . into ( ) ) ;
761
+ }
762
+
763
+ Ok ( found_labels. into_iter ( ) . map ( |s| s. clone ( ) ) . collect ( ) )
764
+ }
765
+
733
766
pub async fn remove_label ( & self , client : & GithubClient , label : & str ) -> anyhow:: Result < ( ) > {
734
767
log:: info!( "remove_label from {}: {:?}" , self . global_id( ) , label) ;
768
+
769
+ let normalized_labels = self
770
+ . normalize_and_match_labels ( client, & [ label. to_string ( ) ] )
771
+ . await ?;
772
+ let label = normalized_labels. first ( ) . unwrap ( ) ;
773
+ log:: info!(
774
+ "remove_label from {}: matched label to {:?}" ,
775
+ self . global_id( ) ,
776
+ label
777
+ ) ;
778
+
735
779
// DELETE /repos/:owner/:repo/issues/:number/labels/{name}
736
780
let url = format ! (
737
781
"{repo_url}/issues/{number}/labels/{name}" ,
@@ -767,6 +811,19 @@ impl Issue {
767
811
labels : Vec < Label > ,
768
812
) -> anyhow:: Result < ( ) > {
769
813
log:: info!( "add_labels: {} +{:?}" , self . global_id( ) , labels) ;
814
+
815
+ let labels = self
816
+ . normalize_and_match_labels (
817
+ client,
818
+ & labels. into_iter ( ) . map ( |l| l. name ) . collect :: < Vec < _ > > ( ) ,
819
+ )
820
+ . await ?;
821
+ log:: info!(
822
+ "add_labels: {} matched requested labels to +{:?}" ,
823
+ self . global_id( ) ,
824
+ labels
825
+ ) ;
826
+
770
827
// POST /repos/:owner/:repo/issues/:number/labels
771
828
// repo_url = https://api.github.com/repos/Codertocat/Hello-World
772
829
let url = format ! (
@@ -778,8 +835,7 @@ impl Issue {
778
835
// Don't try to add labels already present on this issue.
779
836
let labels = labels
780
837
. into_iter ( )
781
- . filter ( |l| !self . labels ( ) . contains ( & l) )
782
- . map ( |l| l. name )
838
+ . filter ( |l| !self . labels ( ) . iter ( ) . any ( |existing| existing. name == * l) )
783
839
. collect :: < Vec < _ > > ( ) ;
784
840
785
841
log:: info!( "add_labels: {} filtered to {:?}" , self . global_id( ) , labels) ;
@@ -788,32 +844,13 @@ impl Issue {
788
844
return Ok ( ( ) ) ;
789
845
}
790
846
791
- let mut unknown_labels = vec ! [ ] ;
792
- let mut known_labels = vec ! [ ] ;
793
- for label in labels {
794
- if !self . repository ( ) . has_label ( client, & label) . await ? {
795
- unknown_labels. push ( label) ;
796
- } else {
797
- known_labels. push ( label) ;
798
- }
799
- }
800
-
801
- if !unknown_labels. is_empty ( ) {
802
- return Err ( UnknownLabels {
803
- labels : unknown_labels,
804
- }
805
- . into ( ) ) ;
806
- }
807
-
808
847
#[ derive( serde:: Serialize ) ]
809
848
struct LabelsReq {
810
849
labels : Vec < String > ,
811
850
}
812
851
813
852
client
814
- . send_req ( client. post ( & url) . json ( & LabelsReq {
815
- labels : known_labels,
816
- } ) )
853
+ . send_req ( client. post ( & url) . json ( & LabelsReq { labels } ) )
817
854
. await
818
855
. context ( "failed to add labels" ) ?;
819
856
0 commit comments