Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • project/automatic_updates
  • issue/automatic_updates-3159719
  • issue/automatic_updates-3227923
  • issue/automatic_updates-3227927
  • issue/automatic_updates-3227122
  • issue/automatic_updates-3227940
  • issue/automatic_updates-3228095
  • issue/automatic_updates-3228101
  • issue/automatic_updates-3228115
  • issue/automatic_updates-3228125
  • issue/automatic_updates-3228359
  • issue/automatic_updates-3228392
  • issue/automatic_updates-3225753
  • issue/automatic_updates-3227588
  • issue/automatic_updates-3228521
  • issue/automatic_updates-3228539
  • issue/automatic_updates-3226570
  • issue/automatic_updates-3228806
  • issue/automatic_updates-3227575
  • issue/automatic_updates-3229485
  • issue/automatic_updates-3228955
  • issue/automatic_updates-3230024
  • issue/automatic_updates-3230250
  • issue/automatic_updates-3230249
  • issue/automatic_updates-3230264
  • issue/automatic_updates-3230510
  • issue/automatic_updates-3230235
  • issue/automatic_updates-3230050
  • issue/automatic_updates-3230934
  • issue/automatic_updates-3230049
  • issue/automatic_updates-3230507
  • issue/automatic_updates-3231153
  • issue/automatic_updates-3231992
  • issue/automatic_updates-3232003
  • issue/automatic_updates-3232004
  • issue/automatic_updates-3231996
  • issue/automatic_updates-3231999
  • issue/automatic_updates-3230668
  • issue/automatic_updates-3232000
  • issue/automatic_updates-3232420
  • issue/automatic_updates-3232729
  • issue/automatic_updates-3232761
  • issue/automatic_updates-3231997
  • issue/automatic_updates-3232927
  • issue/automatic_updates-3232959
  • issue/automatic_updates-3233124
  • issue/automatic_updates-3230856
  • issue/automatic_updates-3233493
  • issue/automatic_updates-3233587
  • issue/automatic_updates-3233521
  • issue/automatic_updates-3236405
  • issue/automatic_updates-3236299
  • issue/automatic_updates-3238586
  • issue/automatic_updates-3238647
  • issue/automatic_updates-3238717
  • issue/automatic_updates-3238846
  • issue/automatic_updates-3238852
  • issue/automatic_updates-3238866
  • issue/automatic_updates-3238714
  • issue/automatic_updates-3239103
  • issue/automatic_updates-3233138
  • issue/automatic_updates-3239466
  • issue/automatic_updates-3239645
  • issue/automatic_updates-3239673
  • issue/automatic_updates-3277775
  • issue/automatic_updates-3280923
  • issue/automatic_updates-3275315
  • issue/automatic_updates-3279527
  • issue/automatic_updates-3279733
  • issue/automatic_updates-3281405
  • issue/automatic_updates-3281397
  • issue/automatic_updates-3280204
  • issue/automatic_updates-3277817
  • issue/automatic_updates-3277230
  • issue/automatic_updates-3276534
  • issue/automatic_updates-3279538
  • issue/automatic_updates-3280168
  • issue/automatic_updates-3271502
  • issue/automatic_updates-3283644
  • issue/automatic_updates-3302524
  • issue/automatic_updates-3275883
  • issue/automatic_updates-3280180
  • issue/automatic_updates-3274837
  • issue/automatic_updates-3281473
  • issue/automatic_updates-3281634
  • issue/automatic_updates-3239889
  • issue/automatic_updates-3240753
  • issue/automatic_updates-3233103
  • issue/automatic_updates-3240971
  • issue/automatic_updates-3233564
  • issue/automatic_updates-3241380
  • issue/automatic_updates-3241105
  • issue/automatic_updates-3242626
  • issue/automatic_updates-3242724
  • issue/automatic_updates-3243057
  • issue/automatic_updates-3243348
  • issue/automatic_updates-3243339
  • issue/automatic_updates-3243422
  • issue/automatic_updates-3243436
  • issue/automatic_updates-3243600
  • issue/automatic_updates-3243851
  • issue/automatic_updates-3243405
  • issue/automatic_updates-3244338
  • issue/automatic_updates-3244337
  • issue/automatic_updates-3244360
  • issue/automatic_updates-3244358
  • issue/automatic_updates-3244679
  • issue/automatic_updates-3244939
  • issue/automatic_updates-3244976
  • issue/automatic_updates-3245376
  • issue/automatic_updates-3245388
  • issue/automatic_updates-3245428
  • issue/automatic_updates-3245655
  • issue/automatic_updates-3245766
  • issue/automatic_updates-3245810
  • issue/automatic_updates-3245846
  • issue/automatic_updates-3246036
  • issue/automatic_updates-3246194
  • issue/automatic_updates-3246203
  • issue/automatic_updates-3246283
  • issue/automatic_updates-3279064
  • issue/automatic_updates-3246420
  • issue/automatic_updates-3246638
  • issue/automatic_updates-3246660
  • issue/automatic_updates-3246673
  • issue/automatic_updates-3246695
  • issue/automatic_updates-3245996
  • issue/automatic_updates-3247308
  • issue/automatic_updates-3247478
  • issue/automatic_updates-3247479
  • issue/automatic_updates-3279086
  • issue/automatic_updates-3248312
  • issue/automatic_updates-3248523
  • issue/automatic_updates-3248527
  • issue/automatic_updates-3244412
  • issue/automatic_updates-3248976
  • issue/automatic_updates-3248909
  • issue/automatic_updates-3246507
  • issue/automatic_updates-3249531
  • issue/automatic_updates-3249517
  • issue/automatic_updates-3249983
  • issue/automatic_updates-3249135
  • issue/automatic_updates-3250136
  • issue/automatic_updates-3250386
  • issue/automatic_updates-3251701
  • issue/automatic_updates-3250696
  • issue/automatic_updates-3248911
  • issue/automatic_updates-3227420
  • issue/automatic_updates-3252071
  • issue/automatic_updates-3252097
  • issue/automatic_updates-3252109
  • issue/automatic_updates-3252126
  • issue/automatic_updates-3249130
  • issue/automatic_updates-3251972
  • issue/automatic_updates-3252187
  • issue/automatic_updates-3252328
  • issue/automatic_updates-3252324
  • issue/automatic_updates-3252533
  • issue/automatic_updates-3253055
  • issue/automatic_updates-3253186
  • issue/automatic_updates-3253395
  • issue/automatic_updates-3253400
  • issue/automatic_updates-3253624
  • issue/automatic_updates-3253647
  • issue/automatic_updates-3253649
  • issue/automatic_updates-3253858
  • issue/automatic_updates-3254166
  • issue/automatic_updates-3254207
  • issue/automatic_updates-3279108
  • issue/automatic_updates-3254606
  • issue/automatic_updates-3254616
  • issue/automatic_updates-3254911
  • issue/automatic_updates-3255011
  • issue/automatic_updates-3255014
  • issue/automatic_updates-3255320
  • issue/automatic_updates-3277562
  • issue/automatic_updates-3255678
  • issue/automatic_updates-3256958
  • issue/automatic_updates-3257115
  • issue/automatic_updates-3257432
  • issue/automatic_updates-3257446
  • issue/automatic_updates-3257473
  • issue/automatic_updates-3257134
  • issue/automatic_updates-3257849
  • issue/automatic_updates-3258065
  • issue/automatic_updates-3258056
  • issue/automatic_updates-3258048
  • issue/automatic_updates-3258045
  • issue/automatic_updates-3258464
  • issue/automatic_updates-3258611
  • issue/automatic_updates-3258646
  • issue/automatic_updates-3258661
  • issue/automatic_updates-3258667
  • issue/automatic_updates-3258590
  • issue/automatic_updates-3259228
  • issue/automatic_updates-3259656
  • issue/automatic_updates-3252299
  • issue/automatic_updates-3259664
  • issue/automatic_updates-3259822
  • issue/automatic_updates-3259810
  • issue/automatic_updates-3259814
  • issue/automatic_updates-3260368
  • issue/automatic_updates-3257845
  • issue/automatic_updates-3260664
  • issue/automatic_updates-3260672
  • issue/automatic_updates-3260698
  • issue/automatic_updates-3260662
  • issue/automatic_updates-3260669
  • issue/automatic_updates-3260668
  • issue/automatic_updates-3261637
  • issue/automatic_updates-3261772
  • issue/automatic_updates-3261847
  • issue/automatic_updates-3262016
  • issue/automatic_updates-3262244
  • issue/automatic_updates-3262303
  • issue/automatic_updates-3261642
  • issue/automatic_updates-3262359
  • issue/automatic_updates-3262542
  • issue/automatic_updates-3261098
  • issue/automatic_updates-3260666
  • issue/automatic_updates-3262587
  • issue/automatic_updates-3262965
  • issue/automatic_updates-3263155
  • issue/automatic_updates-3263223
  • issue/automatic_updates-3254755
  • issue/automatic_updates-3261758
  • issue/automatic_updates-3258059
  • issue/automatic_updates-3259196
  • issue/automatic_updates-3263865
  • issue/automatic_updates-3262284
  • issue/automatic_updates-3264554
  • issue/automatic_updates-3248928
  • issue/automatic_updates-3248929
  • issue/automatic_updates-3265072
  • issue/automatic_updates-3265057
  • issue/automatic_updates-3265873
  • issue/automatic_updates-3266092
  • issue/automatic_updates-3266633
  • issue/automatic_updates-3266640
  • issue/automatic_updates-3266687
  • issue/automatic_updates-3266981
  • issue/automatic_updates-3267387
  • issue/automatic_updates-3267389
  • issue/automatic_updates-3267411
  • issue/automatic_updates-3267603
  • issue/automatic_updates-3265874
  • issue/automatic_updates-3267577
  • issue/automatic_updates-3267386
  • issue/automatic_updates-3268363
  • issue/automatic_updates-3268868
  • issue/automatic_updates-3268612
  • issue/automatic_updates-3269097
  • issue/automatic_updates-3267632
  • issue/automatic_updates-3270736
  • issue/automatic_updates-3271144
  • issue/automatic_updates-3271226
  • issue/automatic_updates-3271078
  • issue/automatic_updates-3271371
  • issue/automatic_updates-3267646
  • issue/automatic_updates-3272060
  • issue/automatic_updates-3265031
  • issue/automatic_updates-3253591
  • issue/automatic_updates-3272520
  • issue/automatic_updates-3272061
  • issue/automatic_updates-3272785
  • issue/automatic_updates-3273011
  • issue/automatic_updates-3273008
  • issue/automatic_updates-3273364
  • issue/automatic_updates-3273407
  • issue/automatic_updates-3271235
  • issue/automatic_updates-3273006
  • issue/automatic_updates-3271240
  • issue/automatic_updates-3271468
  • issue/automatic_updates-3272326
  • issue/automatic_updates-3273693
  • issue/automatic_updates-3264849
  • issue/automatic_updates-3273807
  • issue/automatic_updates-3273017
  • issue/automatic_updates-3274269
  • issue/automatic_updates-3274273
  • issue/automatic_updates-3274292
  • issue/automatic_updates-3273812
  • issue/automatic_updates-3274323
  • issue/automatic_updates-3274633
  • issue/automatic_updates-3274858
  • issue/automatic_updates-3274830
  • issue/automatic_updates-3274892
  • issue/automatic_updates-3275075
  • issue/automatic_updates-3275256
  • issue/automatic_updates-3275282
  • issue/automatic_updates-3275320
  • issue/automatic_updates-3275323
  • issue/automatic_updates-3275324
  • issue/automatic_updates-3275317
  • issue/automatic_updates-3275313
  • issue/automatic_updates-3275357
  • issue/automatic_updates-3275474
  • issue/automatic_updates-3275311
  • issue/automatic_updates-3275546
  • issue/automatic_updates-3275508
  • issue/automatic_updates-3275810
  • issue/automatic_updates-3275860
  • issue/automatic_updates-3275865
  • issue/automatic_updates-3275886
  • issue/automatic_updates-3275887
  • issue/automatic_updates-3276031
  • issue/automatic_updates-3276041
  • issue/automatic_updates-3276072
  • issue/automatic_updates-3276159
  • issue/automatic_updates-3276255
  • issue/automatic_updates-3275825
  • issue/automatic_updates-3276661
  • issue/automatic_updates-3275369
  • issue/automatic_updates-3277014
  • issue/automatic_updates-3277035
  • issue/automatic_updates-3272813
  • issue/automatic_updates-3277211
  • issue/automatic_updates-3277229
  • issue/automatic_updates-3275316
  • issue/automatic_updates-3274047
  • issue/automatic_updates-3277815
  • issue/automatic_updates-3276662
  • issue/automatic_updates-3277235
  • issue/automatic_updates-3278411
  • issue/automatic_updates-3278435
  • issue/automatic_updates-3279229
  • issue/automatic_updates-3277007
  • issue/automatic_updates-3284346
  • issue/automatic_updates-3239759
  • issue/automatic_updates-3284495
  • issue/automatic_updates-3284530
  • issue/automatic_updates-3282677
  • issue/automatic_updates-3281379
  • issue/automatic_updates-3285408
  • issue/automatic_updates-3285631
  • issue/automatic_updates-3285669
  • issue/automatic_updates-3273014
  • issue/automatic_updates-3273369
  • issue/automatic_updates-3285491
  • issue/automatic_updates-3285898
  • issue/automatic_updates-3287251
  • issue/automatic_updates-3291147
  • issue/automatic_updates-3286650
  • issue/automatic_updates-3291770
  • issue/automatic_updates-3280403
  • issue/automatic_updates-3291959
  • issue/automatic_updates-3287398
  • issue/automatic_updates-3285145
  • issue/automatic_updates-3292027
  • issue/automatic_updates-3278445
  • issue/automatic_updates-3292933
  • issue/automatic_updates-3292958
  • issue/automatic_updates-3293157
  • issue/automatic_updates-3292956
  • issue/automatic_updates-3293146
  • issue/automatic_updates-3293381
  • issue/automatic_updates-3293449
  • issue/automatic_updates-3293427
  • issue/automatic_updates-3293422
  • issue/automatic_updates-3293685
  • issue/automatic_updates-3291730
  • issue/automatic_updates-3293866
  • issue/automatic_updates-3294338
  • issue/automatic_updates-3293656
  • issue/automatic_updates-3294600
  • issue/automatic_updates-3294335
  • issue/automatic_updates-3295013
  • issue/automatic_updates-3284936
  • issue/automatic_updates-3295596
  • issue/automatic_updates-3295758
  • issue/automatic_updates-3295791
  • issue/automatic_updates-3295830
  • issue/automatic_updates-3295874
  • issue/automatic_updates-3295897
  • issue/automatic_updates-3296074
  • issue/automatic_updates-3295965
  • issue/automatic_updates-3296181
  • issue/automatic_updates-3296065
  • issue/automatic_updates-3296178
  • issue/automatic_updates-3296261
  • issue/automatic_updates-3293148
  • issue/automatic_updates-3303953
  • issue/automatic_updates-3304367
  • issue/automatic_updates-3304640
  • issue/automatic_updates-3304836
  • issue/automatic_updates-3304142
  • issue/automatic_updates-3298349
  • issue/automatic_updates-3298444
  • issue/automatic_updates-3298510
  • issue/automatic_updates-3298431
  • issue/automatic_updates-3298863
  • issue/automatic_updates-3298877
  • issue/automatic_updates-3298904
  • issue/automatic_updates-3298951
  • issue/automatic_updates-3299101
  • issue/automatic_updates-3299417
  • issue/automatic_updates-3293417
  • issue/automatic_updates-3299612
  • issue/automatic_updates-3299093
  • issue/automatic_updates-3299087
  • issue/automatic_updates-3300006
  • issue/automatic_updates-3300036
  • issue/automatic_updates-3293150
  • issue/automatic_updates-3303807
  • issue/automatic_updates-3303929
  • issue/automatic_updates-3304417
  • issue/automatic_updates-3303727
  • issue/automatic_updates-3305527
  • issue/automatic_updates-3301844
  • issue/automatic_updates-3302527
  • issue/automatic_updates-3302673
  • issue/automatic_updates-3302897
  • issue/automatic_updates-3303143
  • issue/automatic_updates-3303168
  • issue/automatic_updates-3303174
  • issue/automatic_updates-3303185
  • issue/automatic_updates-3303200
  • issue/automatic_updates-3303113
  • issue/automatic_updates-3304036
  • issue/automatic_updates-3304165
  • issue/automatic_updates-3304583
  • issue/automatic_updates-3303902
  • issue/automatic_updates-3304651
  • issue/automatic_updates-3305312
  • issue/automatic_updates-3305612
  • issue/automatic_updates-3305568
  • issue/automatic_updates-3305773
  • issue/automatic_updates-3305874
  • issue/automatic_updates-3310901
  • issue/automatic_updates-3310929
  • issue/automatic_updates-3310936
  • issue/automatic_updates-3310972
  • issue/automatic_updates-3311001
  • issue/automatic_updates-3311020
  • issue/automatic_updates-3305240
  • issue/automatic_updates-3305994
  • issue/automatic_updates-3305564
  • issue/automatic_updates-3305998
  • issue/automatic_updates-3306600
  • issue/automatic_updates-3306631
  • issue/automatic_updates-3307086
  • issue/automatic_updates-3307103
  • issue/automatic_updates-3307168
  • issue/automatic_updates-3306163
  • issue/automatic_updates-3304617
  • issue/automatic_updates-3307163
  • issue/automatic_updates-3307398
  • issue/automatic_updates-3307478
  • issue/automatic_updates-3307369
  • issue/automatic_updates-3308404
  • issue/automatic_updates-3307611
  • issue/automatic_updates-3308372
  • issue/automatic_updates-3308686
  • issue/automatic_updates-3308886
  • issue/automatic_updates-3309220
  • issue/automatic_updates-3309270
  • issue/automatic_updates-3308711
  • issue/automatic_updates-3309486
  • issue/automatic_updates-3305167
  • issue/automatic_updates-3309676
  • issue/automatic_updates-3309025
  • issue/automatic_updates-3309891
  • issue/automatic_updates-3308365
  • issue/automatic_updates-3310000
  • issue/automatic_updates-3309205
  • issue/automatic_updates-3248975
  • issue/automatic_updates-3310702
  • issue/automatic_updates-3272313
  • issue/automatic_updates-3310990
  • issue/automatic_updates-3310997
  • issue/automatic_updates-3311200
  • issue/automatic_updates-3310696
  • issue/automatic_updates-3311265
  • issue/automatic_updates-3303167
  • issue/automatic_updates-3310946
  • issue/automatic_updates-3308828
  • issue/automatic_updates-3310666
  • issue/automatic_updates-3109082
  • issue/automatic_updates-3311534
  • issue/automatic_updates-3311436
  • issue/automatic_updates-3308843
  • issue/automatic_updates-3303900
  • issue/automatic_updates-3312085
  • issue/automatic_updates-3312420
  • issue/automatic_updates-3312373
  • issue/automatic_updates-3312421
  • issue/automatic_updates-3312619
  • issue/automatic_updates-3312779
  • issue/automatic_updates-3275991
  • issue/automatic_updates-3312938
  • issue/automatic_updates-3312937
  • issue/automatic_updates-3312960
  • issue/automatic_updates-3312669
  • issue/automatic_updates-3312981
  • issue/automatic_updates-3313319
  • issue/automatic_updates-3313346
  • issue/automatic_updates-3310914
  • issue/automatic_updates-3317220
  • issue/automatic_updates-3317232
  • issue/automatic_updates-3317278
  • issue/automatic_updates-3317409
  • issue/automatic_updates-3317267
  • issue/automatic_updates-3317599
  • issue/automatic_updates-3317988
  • issue/automatic_updates-3318313
  • issue/automatic_updates-3313630
  • issue/automatic_updates-3313717
  • issue/automatic_updates-3313947
  • issue/automatic_updates-3313507
  • issue/automatic_updates-3276645
  • issue/automatic_updates-3313349
  • issue/automatic_updates-3314137
  • issue/automatic_updates-3314143
  • issue/automatic_updates-3304365
  • issue/automatic_updates-3317796
  • issue/automatic_updates-3317996
  • issue/automatic_updates-3316484
  • issue/automatic_updates-3313414
  • issue/automatic_updates-3314764
  • issue/automatic_updates-3314771
  • issue/automatic_updates-3314787
  • issue/automatic_updates-3314805
  • issue/automatic_updates-3314734
  • issue/automatic_updates-3298889
  • issue/automatic_updates-3314803
  • issue/automatic_updates-3315139
  • issue/automatic_updates-3314946
  • issue/automatic_updates-3315449
  • issue/automatic_updates-3315798
  • issue/automatic_updates-3315834
  • issue/automatic_updates-3309602
  • issue/automatic_updates-3316115
  • issue/automatic_updates-3316131
  • issue/automatic_updates-3316293
  • issue/automatic_updates-3316318
  • issue/automatic_updates-3310729
  • issue/automatic_updates-3315700
  • issue/automatic_updates-3316668
  • issue/automatic_updates-3316721
  • issue/automatic_updates-3306283
  • issue/automatic_updates-3316611
  • issue/automatic_updates-3316895
  • issue/automatic_updates-3317385
  • issue/automatic_updates-3318625
  • issue/automatic_updates-3318770
  • issue/automatic_updates-3318846
  • issue/automatic_updates-3318927
  • issue/automatic_updates-3318933
  • issue/automatic_updates-3319044
  • issue/automatic_updates-3319045
  • issue/automatic_updates-3325716
  • issue/automatic_updates-3324421
  • issue/automatic_updates-3320486
  • issue/automatic_updates-3320558
  • issue/automatic_updates-3321206
  • issue/automatic_updates-3326934
  • issue/automatic_updates-3320755
  • issue/automatic_updates-3328765
  • issue/automatic_updates-3325654
  • issue/automatic_updates-3325869
  • issue/automatic_updates-3277034
  • issue/automatic_updates-3328516
  • issue/automatic_updates-3322546
  • issue/automatic_updates-3320487
  • issue/automatic_updates-3320792
  • issue/automatic_updates-3320815
  • issue/automatic_updates-3320638
  • issue/automatic_updates-3321256
  • issue/automatic_updates-3321684
  • issue/automatic_updates-3325522
  • issue/automatic_updates-3326334
  • issue/automatic_updates-3328234
  • issue/automatic_updates-3328740
  • issue/automatic_updates-3316932
  • issue/automatic_updates-3321386
  • issue/automatic_updates-3321933
  • issue/automatic_updates-3321282
  • issue/automatic_updates-3326801
  • issue/automatic_updates-3327753
  • issue/automatic_updates-3328742
  • issue/automatic_updates-3322313
  • issue/automatic_updates-3322203
  • issue/automatic_updates-3322150
  • issue/automatic_updates-3322404
  • issue/automatic_updates-3321236
  • issue/automatic_updates-3321994
  • issue/automatic_updates-3322589
  • issue/automatic_updates-3321904
  • issue/automatic_updates-3316855
  • issue/automatic_updates-3322931
  • issue/automatic_updates-3322913
  • issue/automatic_updates-3323037
  • issue/automatic_updates-3322918
  • issue/automatic_updates-3323211
  • issue/automatic_updates-3323565
  • issue/automatic_updates-3320824
  • issue/automatic_updates-3320836
  • issue/automatic_updates-3329002
  • issue/automatic_updates-3321971
  • issue/automatic_updates-3299094
  • issue/automatic_updates-3248544
  • issue/automatic_updates-3330139
  • issue/automatic_updates-3330365
  • issue/automatic_updates-3330712
  • issue/automatic_updates-3318065
  • issue/automatic_updates-3331310
  • issue/automatic_updates-3322917
  • issue/automatic_updates-3330140
  • issue/automatic_updates-3323003
  • issue/automatic_updates-3331355
  • issue/automatic_updates-3334054
  • issue/automatic_updates-3323706
  • issue/automatic_updates-3331168
  • issue/automatic_updates-3343430
  • issue/automatic_updates-3344562
  • issue/automatic_updates-3345039
  • issue/automatic_updates-3344039
  • issue/automatic_updates-3345313
  • issue/automatic_updates-3331471
  • issue/automatic_updates-3334994
  • issue/automatic_updates-3327229
  • issue/automatic_updates-3343889
  • issue/automatic_updates-3344124
  • issue/automatic_updates-3344689
  • issue/automatic_updates-3316368
  • issue/automatic_updates-3345767
  • issue/automatic_updates-3317815
  • issue/automatic_updates-3328600
  • issue/automatic_updates-3332256
  • issue/automatic_updates-3335766
  • issue/automatic_updates-3335802
  • issue/automatic_updates-3335908
  • issue/automatic_updates-3336243
  • issue/automatic_updates-3336247
  • issue/automatic_updates-3336255
  • issue/automatic_updates-3336259
  • issue/automatic_updates-3337062
  • issue/automatic_updates-3337068
  • issue/automatic_updates-3311229
  • issue/automatic_updates-3337697
  • issue/automatic_updates-3343768
  • issue/automatic_updates-3344127
  • issue/automatic_updates-3344595
  • issue/automatic_updates-3345763
  • issue/automatic_updates-3345765
  • issue/automatic_updates-3345771
  • issue/automatic_updates-3337760
  • issue/automatic_updates-3337049
  • issue/automatic_updates-3342430
  • issue/automatic_updates-3319507
  • issue/automatic_updates-3344583
  • issue/automatic_updates-3345764
  • issue/automatic_updates-3345768
  • issue/automatic_updates-3338667
  • issue/automatic_updates-3339016
  • issue/automatic_updates-3343827
  • issue/automatic_updates-3344556
  • issue/automatic_updates-3338666
  • issue/automatic_updates-3345649
  • issue/automatic_updates-3339657
  • issue/automatic_updates-3339719
  • issue/automatic_updates-3338789
  • issue/automatic_updates-3340022
  • issue/automatic_updates-3340284
  • issue/automatic_updates-3340638
  • issue/automatic_updates-3316617
  • issue/automatic_updates-3341224
  • issue/automatic_updates-3337953
  • issue/automatic_updates-3339714
  • issue/automatic_updates-3341708
  • issue/automatic_updates-3341841
  • issue/automatic_updates-3326486
  • issue/automatic_updates-3321474
  • issue/automatic_updates-3341974
  • issue/automatic_updates-3342120
  • issue/automatic_updates-3342227
  • issue/automatic_updates-3323461
  • issue/automatic_updates-3342137
  • issue/automatic_updates-3342460
  • issue/automatic_updates-3342364
  • issue/automatic_updates-3345028
  • issue/automatic_updates-3345549
  • issue/automatic_updates-3345881
  • issue/automatic_updates-3345762
  • issue/automatic_updates-3342726
  • issue/automatic_updates-3337667
  • issue/automatic_updates-3343463
  • issue/automatic_updates-3345766
  • issue/automatic_updates-3345754
  • issue/automatic_updates-3345633
  • issue/automatic_updates-3345772
  • issue/automatic_updates-3345769
  • issue/automatic_updates-3345761
  • issue/automatic_updates-3345755
  • issue/automatic_updates-3345760
  • issue/automatic_updates-3345757
  • issue/automatic_updates-3346520
  • issue/automatic_updates-3346545
  • issue/automatic_updates-3337054
  • issue/automatic_updates-3346628
  • issue/automatic_updates-3346717
  • issue/automatic_updates-3346594
  • issue/automatic_updates-3346659
  • issue/automatic_updates-3347031
  • issue/automatic_updates-3346547
  • issue/automatic_updates-3347164
  • issue/automatic_updates-3318306
  • issue/automatic_updates-3345646
  • issue/automatic_updates-3347165
  • issue/automatic_updates-3347959
  • issue/automatic_updates-3347267
  • issue/automatic_updates-3338346
  • issue/automatic_updates-3348122
  • issue/automatic_updates-3348129
  • issue/automatic_updates-3348162
  • issue/automatic_updates-3348276
  • issue/automatic_updates-3348441
  • issue/automatic_updates-3351604
  • issue/automatic_updates-3316843
  • issue/automatic_updates-3351908
  • issue/automatic_updates-3352731
  • issue/automatic_updates-3352898
  • issue/automatic_updates-3348866
  • issue/automatic_updates-3342817
  • issue/automatic_updates-3348159
  • issue/automatic_updates-3349142
  • issue/automatic_updates-3349966
  • issue/automatic_updates-3350909
  • issue/automatic_updates-3351594
  • issue/automatic_updates-3351883
  • issue/automatic_updates-3351925
  • issue/automatic_updates-3352198
  • issue/automatic_updates-3334552
  • issue/automatic_updates-3342790
  • issue/automatic_updates-3345222
  • issue/automatic_updates-3351212
  • issue/automatic_updates-3351093
  • issue/automatic_updates-3351247
  • issue/automatic_updates-3351962
  • issue/automatic_updates-3345641
  • issue/automatic_updates-3338940
  • issue/automatic_updates-3343721
  • issue/automatic_updates-3351069
  • issue/automatic_updates-3353219
  • issue/automatic_updates-3159920
  • issue/automatic_updates-3352355
  • issue/automatic_updates-3341469
  • issue/automatic_updates-3354099
  • issue/automatic_updates-3339659
  • issue/automatic_updates-3354312
  • issue/automatic_updates-3351895
  • issue/automatic_updates-3354249
  • issue/automatic_updates-3353270
  • issue/automatic_updates-3354003
  • issue/automatic_updates-3355074
  • issue/automatic_updates-3355094
  • issue/automatic_updates-3355105
  • issue/automatic_updates-3355162
  • issue/automatic_updates-3355446
  • issue/automatic_updates-3355045
  • issue/automatic_updates-3355558
  • issue/automatic_updates-3355553
  • issue/automatic_updates-3354594
  • issue/automatic_updates-3355609
  • issue/automatic_updates-3354325
  • issue/automatic_updates-3356395
  • issue/automatic_updates-3356638
  • issue/automatic_updates-3356640
  • issue/automatic_updates-3354701
  • issue/automatic_updates-3355463
  • issue/automatic_updates-3356804
  • issue/automatic_updates-3357721
  • issue/automatic_updates-3357578
  • issue/automatic_updates-3357657
  • issue/automatic_updates-3304108
  • issue/automatic_updates-3357941
  • issue/automatic_updates-3358012
  • issue/automatic_updates-3345535
  • issue/automatic_updates-3354827
  • issue/automatic_updates-3358570
  • issue/automatic_updates-3358878
  • issue/automatic_updates-3355628
  • issue/automatic_updates-3358504
  • issue/automatic_updates-3357969
  • issue/automatic_updates-3359609
  • issue/automatic_updates-3359727
  • issue/automatic_updates-3359825
  • issue/automatic_updates-3359820
  • issue/automatic_updates-3360548
  • issue/automatic_updates-3357480
  • issue/automatic_updates-3360656
  • issue/automatic_updates-3360763
  • issue/automatic_updates-3360655
  • issue/automatic_updates-3355618
  • issue/automatic_updates-3349351
  • issue/automatic_updates-3362110
  • issue/automatic_updates-3362143
  • issue/automatic_updates-3362589
  • issue/automatic_updates-3349004
  • issue/automatic_updates-3362507
  • issue/automatic_updates-3362746
  • issue/automatic_updates-3362978
  • issue/automatic_updates-3363259
  • issue/automatic_updates-3363725
  • issue/automatic_updates-3363926
  • issue/automatic_updates-3363937
  • issue/automatic_updates-3364514
  • issue/automatic_updates-3284443
  • issue/automatic_updates-3363938
  • issue/automatic_updates-3364731
  • issue/automatic_updates-3364748
  • issue/automatic_updates-3364735
  • issue/automatic_updates-3364725
  • issue/automatic_updates-3359670
  • issue/automatic_updates-3365133
  • issue/automatic_updates-3365151
  • issue/automatic_updates-3365177
  • issue/automatic_updates-3364958
  • issue/automatic_updates-3365390
  • issue/automatic_updates-3365414
  • issue/automatic_updates-3346644
  • issue/automatic_updates-3366267
  • issue/automatic_updates-3366289
  • issue/automatic_updates-3366499
  • issue/automatic_updates-3365437
  • issue/automatic_updates-3354914
  • issue/automatic_updates-3253828
  • issue/automatic_updates-3345484
  • issue/automatic_updates-3352846
  • issue/automatic_updates-3368741
  • issue/automatic_updates-3406008
  • issue/automatic_updates-3406122
  • issue/automatic_updates-3370197
  • issue/automatic_updates-3366271
  • issue/automatic_updates-3364135
  • issue/automatic_updates-3370603
  • issue/automatic_updates-3400146
  • issue/automatic_updates-3406010
  • issue/automatic_updates-3404429
  • issue/automatic_updates-3371212
  • issue/automatic_updates-3371076
  • issue/automatic_updates-3372673
  • issue/automatic_updates-3368808
  • issue/automatic_updates-3374175
  • issue/automatic_updates-3374739
  • issue/automatic_updates-3374753
  • issue/automatic_updates-3375679
  • issue/automatic_updates-3377237
  • issue/automatic_updates-3377245
  • issue/automatic_updates-3378774
  • issue/automatic_updates-3378793
  • issue/automatic_updates-3360485
  • issue/automatic_updates-3380698
  • issue/automatic_updates-3375940
  • issue/automatic_updates-3381294
  • issue/automatic_updates-3381484
  • issue/automatic_updates-3382942
  • issue/automatic_updates-3383462
  • issue/automatic_updates-3383451
  • issue/automatic_updates-3384359
  • issue/automatic_updates-3384637
  • issue/automatic_updates-3375640
  • issue/automatic_updates-3386135
  • issue/automatic_updates-3386533
  • issue/automatic_updates-3387610
  • issue/automatic_updates-3387637
  • issue/automatic_updates-3387656
  • issue/automatic_updates-3389157
  • issue/automatic_updates-3388965
  • issue/automatic_updates-3387379
  • issue/automatic_updates-3390493
  • issue/automatic_updates-3308235
  • issue/automatic_updates-3391715
  • issue/automatic_updates-3392246
  • issue/automatic_updates-3393633
  • issue/automatic_updates-3379344
  • issue/automatic_updates-3394413
  • issue/automatic_updates-3394705
  • issue/automatic_updates-3394936
  • issue/automatic_updates-3363497
  • issue/automatic_updates-3395782
  • issue/automatic_updates-3341406
  • issue/automatic_updates-3311472
  • issue/automatic_updates-3397228
  • issue/automatic_updates-3352654
  • issue/automatic_updates-3398782
  • issue/automatic_updates-3399147
  • issue/automatic_updates-3399155
  • issue/automatic_updates-3406812
  • issue/automatic_updates-3408483
  • issue/automatic_updates-3408488
  • issue/automatic_updates-3408560
  • issue/automatic_updates-3437633
  • issue/automatic_updates-3408835
  • issue/automatic_updates-3408937
  • issue/automatic_updates-3409491
  • issue/automatic_updates-3409519
  • issue/automatic_updates-3377458
  • issue/automatic_updates-3437951
  • issue/automatic_updates-3409774
  • issue/automatic_updates-3410047
  • issue/automatic_updates-3411110
  • issue/automatic_updates-3411241
  • issue/automatic_updates-3411276
  • issue/automatic_updates-3411392
  • issue/automatic_updates-3411240
  • issue/automatic_updates-3412630
  • issue/automatic_updates-3413866
  • issue/automatic_updates-3414168
  • issue/automatic_updates-3415291
  • issue/automatic_updates-3415389
  • issue/automatic_updates-3415403
  • issue/automatic_updates-3416768
  • issue/automatic_updates-3417905
  • issue/automatic_updates-3420188
  • issue/automatic_updates-3421641
  • issue/automatic_updates-3424290
  • issue/automatic_updates-3426229
  • issue/automatic_updates-3426666
  • issue/automatic_updates-3426716
  • issue/automatic_updates-3428199
  • issue/automatic_updates-3436741
  • issue/automatic_updates-3441923
  • issue/automatic_updates-3428651
  • issue/automatic_updates-3441799
  • issue/automatic_updates-3443249
  • issue/automatic_updates-3428922
  • issue/automatic_updates-3441817
  • issue/automatic_updates-3429248
  • issue/automatic_updates-3440646
  • issue/automatic_updates-3437877
  • issue/automatic_updates-3445149
  • issue/automatic_updates-3429268
  • issue/automatic_updates-3442153
  • issue/automatic_updates-3438156
  • issue/automatic_updates-3436993
  • issue/automatic_updates-3437704
  • issue/automatic_updates-3432496
  • issue/automatic_updates-3440006
  • issue/automatic_updates-3435893
  • issue/automatic_updates-3441577
  • issue/automatic_updates-3437023
  • issue/automatic_updates-3432391
  • issue/automatic_updates-3432447
  • issue/automatic_updates-3432472
  • issue/automatic_updates-3432489
  • issue/automatic_updates-3432476
  • issue/automatic_updates-3435918
  • issue/automatic_updates-3439067
  • issue/automatic_updates-3437409
  • issue/automatic_updates-3449093
  • issue/automatic_updates-3449480
  • issue/automatic_updates-3449519
  • issue/automatic_updates-3449631
  • issue/automatic_updates-3449636
  • issue/automatic_updates-3449689
  • issue/automatic_updates-3449693
  • issue/automatic_updates-3450884
  • issue/automatic_updates-3452870
  • issue/automatic_updates-3453030
  • issue/automatic_updates-3455161
  • issue/automatic_updates-3459557
  • issue/automatic_updates-3462138
  • issue/automatic_updates-3462168
  • issue/automatic_updates-3462883
  • issue/automatic_updates-3463662
  • issue/automatic_updates-3463813
  • issue/automatic_updates-3446371
  • issue/automatic_updates-3465155
  • issue/automatic_updates-3340355
  • issue/automatic_updates-3467749
  • issue/automatic_updates-3473410
  • issue/automatic_updates-3441926
  • issue/automatic_updates-3474603
  • issue/automatic_updates-3484802
  • issue/automatic_updates-3498586
  • issue/automatic_updates-3499481
  • issue/automatic_updates-3498018
  • issue/automatic_updates-3506632
  • issue/automatic_updates-3511959
  • issue/automatic_updates-3511943
981 results
Show changes
Commits on Source (391)
Showing
with 968 additions and 417 deletions
{
"dictionaries": ["drupal","companies", "fonts", "html", "php", "softwareTerms", "automatic_updates"],
"dictionaryDefinitions": [
{ "name": "automatic_updates", "path": "./dictionary.txt"}
],
"ignorePaths": [
"README.md"
]
}
################
# DrupalCI GitLabCI template
#
# Gitlab-ci.yml to replicate DrupalCI testing for Contrib
#
# With thanks to:
# * The GitLab Acceleration Initiative participants
# * DrupalSpoons
################
################
# Guidelines
#
# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained.
#
# However, you can modify this template if you have additional needs for your project.
################
################
# Includes
#
# Additional configuration can be provided through includes.
# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include.
#
# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml
# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
################
include:
################
# DrupalCI includes:
# As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
# View these include files at https://git.drupalcode.org/project/gitlab_templates/
################
- project: $_GITLAB_TEMPLATES_REPO
# "ref" value can be:
# - Recommended (default) - `ref: $_GITLAB_TEMPLATES_REF` - The Drupal Association will update this value to the recommended tag for contrib.
# - Latest - `ref: main` - Get the latest additions and bug fixes as they are merged into the templates.
# - Minor or Major latests - `ref: 1.x-latest` or `ref: 1.0.x-latest` - Get the latest additions within a minor (mostly bugfixes) or major (bugs and new features).
# - Fixed tag - `ref: 1.0.1` - Set the value to a known tag. This will not get any updates.
# If you change the default value of ref, you should set the _CURL_TEMPLATES_REF variable in the variables section to be the same as set here.
ref: $_GITLAB_TEMPLATES_REF
file:
- '/includes/include.drupalci.main.yml'
# EXPERIMENTAL: For Drupal 7, remove the above line and uncomment the below.
# - '/includes/include.drupalci.main-d7.yml'
- '/includes/include.drupalci.variables.yml'
- '/includes/include.drupalci.workflows.yml'
################
# Pipeline configuration variables
#
# These are the variables provided to the Run Pipeline form that a user may want to override.
#
# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml
################
variables:
_PHPUNIT_CONCURRENT: '1'
_PHPUNIT_TESTGROUPS: ''
# Always test against the previous minor version of core.
OPT_IN_TEST_PREVIOUS_MINOR: '1'
# @todo Remove this line when https://drupal.org/i/3414093 is fixed.
CI_DEBUG_SERVICES: "true"
_TARGET_DB_TYPE: "mariadb"
_TARGET_DB_VERSION: "10.6"
# Always show environment variables, to help in debugging CI problems.
_SHOW_ENVIRONMENT_VARIABLES: '1'
###################################################################################
#
# *
# /(
# ((((,
# /(((((((
# ((((((((((*
# ,(((((((((((((((
# ,(((((((((((((((((((
# ((((((((((((((((((((((((*
# *(((((((((((((((((((((((((((((
# ((((((((((((((((((((((((((((((((((*
# *(((((((((((((((((( .((((((((((((((((((
# ((((((((((((((((((. /(((((((((((((((((*
# /((((((((((((((((( .(((((((((((((((((,
# ,(((((((((((((((((( ((((((((((((((((((
# .(((((((((((((((((((( .(((((((((((((((((
# ((((((((((((((((((((((( ((((((((((((((((/
# (((((((((((((((((((((((((((/ ,(((((((((((((((*
# .((((((((((((((/ /(((((((((((((. ,(((((((((((((((
# *(((((((((((((( ,(((((((((((((/ *((((((((((((((.
# ((((((((((((((, /(((((((((((((. ((((((((((((((,
# (((((((((((((/ ,(((((((((((((* ,(((((((((((((,
# *((((((((((((( .((((((((((((((( ,(((((((((((((
# ((((((((((((/ /((((((((((((((((((. ,((((((((((((/
# ((((((((((((( *(((((((((((((((((((((((* *((((((((((((
# ((((((((((((( ,(((((((((((((..((((((((((((( *((((((((((((
# ((((((((((((, /((((((((((((* /((((((((((((/ ((((((((((((
# ((((((((((((( /((((((((((((/ (((((((((((((* ((((((((((((
# (((((((((((((/ /(((((((((((( ,((((((((((((, *((((((((((((
# (((((((((((((( *(((((((((((/ *((((((((((((. ((((((((((((/
# *((((((((((((((((((((((((((, /(((((((((((((((((((((((((
# ((((((((((((((((((((((((( ((((((((((((((((((((((((,
# .(((((((((((((((((((((((/ ,(((((((((((((((((((((((
# ((((((((((((((((((((((/ ,(((((((((((((((((((((/
# *((((((((((((((((((((( (((((((((((((((((((((,
# ,(((((((((((((((((((((, ((((((((((((((((((((/
# ,(((((((((((((((((((((* /((((((((((((((((((((
# ((((((((((((((((((((((, ,/((((((((((((((((((((,
# ,(((((((((((((((((((((((((((((((((((((((((((((((((((
# .(((((((((((((((((((((((((((((((((((((((((((((
# .((((((((((((((((((((((((((((((((((((,.
# .,(((((((((((((((((((((((((.
#
###################################################################################
.extra-variables: &extra-variables
- MODULE_DIR=$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME
# Strip the `-dev` suffix out of $_TARGET_CORE to reveal the name of the Drupal core branch.
- CORE_BRANCH=`echo $_TARGET_CORE | sed -e "s/-dev$//"`
composer:
extends: .composer-base
before_script:
# In this case, we want to archive everything.
- rm .gitattributes
- composer archive --file=module
after_script:
- *extra-variables
- rm -r -f $MODULE_DIR
- mkdir -p $MODULE_DIR
- tar -x -f module.tar -C $MODULE_DIR
- rm module.tar
- git clone https://git.drupalcode.org/project/drupal.git --depth 1 --branch $CORE_BRANCH /tmp/drupal-core
- cp -R /tmp/drupal-core/composer .
# Copy to web as well as \Drupal\Composer\Composer will expect it be there.
- cp -R /tmp/drupal-core/composer web
composer (previous minor):
before_script:
- !reference [composer, before_script]
after_script:
- !reference [composer, after_script]
composer (previous minor):
before_script:
- !reference [composer, before_script]
after_script:
- !reference [composer, after_script]
phpcs:
before_script:
# Use core's PHPCS configuration.
- cp $_WEB_ROOT/core/phpcs.xml.dist .
phpstan:
before_script:
- *extra-variables
# Ensure our PHPStan configuration has the correct include path to core's PHPStan configuration.
- sed -i "s#%rootDir%/../../../#%rootDir%/../../../$_WEB_ROOT/#" $MODULE_DIR/phpstan.neon
phpunit:
parallel:
matrix:
- MODULE:
- automatic_updates_extensions
- automatic_updates
- package_manager
TEST_TYPE:
- Unit
- Kernel
- Functional
- FunctionalJavascript
- Build
rules:
# Automatic Updates Extensions has no unit or functional JavaScript tests.
- if: $MODULE == "automatic_updates_extensions" && ($TEST_TYPE == "Unit" || $TEST_TYPE == "FunctionalJavascript")
when: never
# Package Manager has no functional JavaScript tests.
- if: $MODULE == "package_manager" && $TEST_TYPE == "FunctionalJavascript"
when: never
- when: on_success
variables:
_PHPUNIT_EXTRA: '--types PHPUnit-$TEST_TYPE --module $MODULE'
phpunit (previous minor):
rules:
- !reference [phpunit, rules]
...@@ -7,7 +7,7 @@ A simple setup is enough for most users to start contributing to Automatic Updat ...@@ -7,7 +7,7 @@ A simple setup is enough for most users to start contributing to Automatic Updat
Assuming you meet the [system requirements](#system-requirements), simply run the following command <sup>[[1]](#footnote-1), [[2]](#footnote-2)</sup> from the directory you would like to install your development environment under <sup>[[3]](#footnote-3)</sup>. It will prompt for confirmation before beginning. Assuming you meet the [system requirements](#system-requirements), simply run the following command <sup>[[1]](#footnote-1), [[2]](#footnote-2)</sup> from the directory you would like to install your development environment under <sup>[[3]](#footnote-3)</sup>. It will prompt for confirmation before beginning.
```shell ```shell
/bin/bash -c "$(curl -fsSL https://git.drupalcode.org/project/automatic_updates/-/raw/8.x-2.x/scripts/setup_local_dev.sh)" /bin/bash -c "$(curl -fsSL https://git.drupalcode.org/project/automatic_updates/-/raw/3.0.x/scripts/setup_local_dev.sh)"
``` ```
That's it. The success message will display next steps. That's it. The success message will display next steps.
...@@ -26,38 +26,35 @@ That's it. The success message will display next steps. ...@@ -26,38 +26,35 @@ That's it. The success message will display next steps.
### System requirements ### System requirements
* A *nix-based operating system, such as Linux or macOS. * A *nix-based operating system, such as Linux or macOS.
* PHP 7.4 or later. * Drupal 10 or later.
* Composer 2 or later. (Automatic Updates is not compatible with Composer 1.) * Composer 2 or later. (Automatic Updates is not compatible with Composer 1.)
* Git must be installed. * Git must be installed.
### Caveats
* Symbolic links are not currently supported by Package Manager. If the Drupal core checkout created during setup below contains any, tests will not work. By default, there shouldn't be any, but some may exist in certain situations (for example, if Drush or Drupal core's JavaScript dependencies have been installed).<br>
<br>
To scan for symbolic links, run `find . -type l` from the Drupal core repository root. If you find any, try to identify their source and eliminate it (for example, by removing a Composer library that places them).
### Customizing your setup ### Customizing your setup
Several details of your setup can be customized via environment variables. Set these before running [the installation command above](#local-development-environment-setup). Several details of your setup can be customized via environment variables. Set these before running [the installation command above](#local-development-environment-setup).
```shell ```shell
DRUPAL_CORE_BRANCH="9.5.x" # The branch of Drupal core that will be installed. DRUPAL_CORE_BRANCH="10.0.x" # The branch of Drupal core that will be installed.
DRUPAL_CORE_SHALLOW_CLONE="TRUE" # Whether or not to do a "shallow clone" of Drupal core. (Defaults to TRUE.) See note below. DRUPAL_CORE_SHALLOW_CLONE="TRUE" # Whether or not to do a "shallow clone" of Drupal core. (Defaults to TRUE.) See note below.
AUTOMATIC_UPDATES_BRANCH="8.x-2.x" # The branch of the Automatic Updates module that will be installed. AUTOMATIC_UPDATES_BRANCH="3.0.x" # The branch of the Automatic Updates module that will be installed.
SITE_DIRECTORY="auto_updates_dev" # The path to the directory where the dev environment will be installed. SITE_DIRECTORY="auto_updates_dev" # The path to the directory where the dev environment will be installed.
SITE_HOST=".test" # The path for Drupal's TRUSTED_HOST_PATTERN. SITE_HOST=".test" # The path for Drupal's TRUSTED_HOST_PATTERN.
``` ```
Note: A shallow Git clone is much smaller and therefore faster, but it removes the ability to do debugging operations such as `git bisect` or `git blame`. To recover these abilities, [you can convert your repository to a full clone after the fact](https://stackoverflow.com/questions/6802145/how-to-convert-a-git-shallow-clone-to-a-full-clone): Note: A shallow Git clone is much smaller and therefore faster, but it removes the ability to do debugging operations such as `git bisect` or `git blame`. To recover these abilities, [you can convert your repository to a full clone after the fact](https://stackoverflow.com/questions/6802145/how-to-convert-a-git-shallow-clone-to-a-full-clone):
// cSpell:disable
```shell ```shell
cd auto-updates-dev cd auto-updates-dev
git fetch --unshallow git fetch --unshallow
``` ```
// cSpell:enable
### Alternative setup options ### Alternative setup options
You can download the setup script first to review its contents or modify it before running it: You can download the setup script first to review its contents or modify it before running it:
```shell ```shell
curl --output setup_local_dev.sh https://git.drupalcode.org/project/automatic_updates/-/raw/8.x-2.x/scripts/setup_local_dev.sh curl --output setup_local_dev.sh https://git.drupalcode.org/project/automatic_updates/-/raw/3.0.x/scripts/setup_local_dev.sh
./scripts/setup_local_dev.sh ./scripts/setup_local_dev.sh
``` ```
...@@ -80,7 +77,7 @@ To set up your environment manually, use the setup script as a reference. ...@@ -80,7 +77,7 @@ To set up your environment manually, use the setup script as a reference.
--- ---
<a name="footnote-1"><sup>1</sup></a> When running a script downloaded from the Web, it is always wise to read its contents first. Ours ([`setup_local_dev.sh`](https://git.drupalcode.org/project/automatic_updates/-/raw/8.x-2.x/scripts/setup_local_dev.sh)) is documented with code comments to help with that. For a more cautious approach, see [Alternative setup options](#alternative-setup-options) above. <a name="footnote-1"><sup>1</sup></a> When running a script downloaded from the Web, it is always wise to read its contents first. Ours ([`setup_local_dev.sh`](https://git.drupalcode.org/project/automatic_updates/-/raw/3.0.x/scripts/setup_local_dev.sh)) is documented with code comments to help with that. For a more cautious approach, see [Alternative setup options](#alternative-setup-options) above.
<a name="footnote-2"><sup>2</sup></a> Curl options explained: <a name="footnote-2"><sup>2</sup></a> Curl options explained:
[`-f, --fail`](https://curl.se/docs/manpage.html#-f), [`-f, --fail`](https://curl.se/docs/manpage.html#-f),
......
...@@ -11,7 +11,7 @@ Follow and read more on the [Automatic Updates Initiative overview and roadmap]( ...@@ -11,7 +11,7 @@ Follow and read more on the [Automatic Updates Initiative overview and roadmap](
Automatic Updates includes a sub-module, Automatic Updates Extensions, which supports updating contributed modules and themes. Automatic Updates includes a sub-module, Automatic Updates Extensions, which supports updating contributed modules and themes.
⚠️ ☢️️ **Automatic Updates Extensions is still experimental and under heavy development.** We encourage you to test it in your local development environment, or another low-stakes testing situation, but it is emphatically NOT ready for use in a production environment. ☢️ ⚠️ ⚠️ ☢️️ Depending on the contributed module or theme, updates to these projects could make the site inoperable. If possible, it is highly recommended that you test these updates in a development environment first.☢️ ⚠️
### More info ### More info
......
#!/usr/bin/env php
<?php
/**
* @file
* Provides a terminal command for performing automatic updates.
*/
use Drupal\automatic_updates\Commands\PostApplyCommand;
use Drupal\automatic_updates\Commands\RunCommand;
use Symfony\Component\Console\Application;
if (PHP_SAPI !== 'cli') {
throw new \RuntimeException('This command must be run from the command line.');
}
// Find the autoloader. We know that Automatic Updates is installed somewhere
// in a Drupal code base, so move up the file system until we find
// `./vendor/autoload.php`.
$current_dir = __DIR__;
$previous_dir = NULL;
while ($current_dir !== $previous_dir) {
$file = $current_dir . '/vendor/autoload.php';
if (file_exists($file)) {
/** @var \Composer\Autoload\ClassLoader $autoloader */
$autoloader = require_once $file;
break;
}
$previous_dir = $current_dir;
$current_dir = dirname($current_dir);
}
if (empty($autoloader)) {
throw new \RuntimeException('The autoloader could not be found. Did you run `composer install`?');
}
// Automatic Updates' namespace is not available for autoloading because it is
// a Drupal module, which means Drupal must be booted up in order to access it.
// Since Drupal isn't booted yet, we need to make the autoloader aware of the
// command namespace.
$autoloader->addPsr4('Drupal\\automatic_updates\\Commands\\', __DIR__ . '/src/Commands');
$application = new Application('Automatic Updates', '3.0.0');
$application->add(new RunCommand($autoloader));
$application->add(new PostApplyCommand($autoloader));
$application->setDefaultCommand('run')->run();
name: 'Automatic Updates' name: 'Automatic Updates'
type: module type: module
description: 'Automatically updates Drupal core.' description: 'Automatically updates Drupal core.'
core_version_requirement: ^9.3 core_version_requirement: ^10.1
dependencies: dependencies:
- drupal:package_manager - drupal:package_manager
- drupal:update - drupal:update
...@@ -5,13 +5,18 @@ ...@@ -5,13 +5,18 @@
* Contains install and update functions for Automatic Updates. * Contains install and update functions for Automatic Updates.
*/ */
declare(strict_types = 1);
use Drupal\automatic_updates\CronUpdateRunner;
use Drupal\automatic_updates\UpdateStage;
use Drupal\automatic_updates\Validation\StatusCheckRequirements; use Drupal\automatic_updates\Validation\StatusCheckRequirements;
use Drupal\system\SystemManager;
/** /**
* Implements hook_uninstall(). * Implements hook_uninstall().
*/ */
function automatic_updates_uninstall() { function automatic_updates_uninstall() {
\Drupal::service('automatic_updates.updater')->destroy(TRUE); \Drupal::service(UpdateStage::class)->destroy(TRUE);
} }
/** /**
...@@ -22,17 +27,28 @@ function automatic_updates_requirements($phase) { ...@@ -22,17 +27,28 @@ function automatic_updates_requirements($phase) {
// Check that site is ready to perform automatic updates. // Check that site is ready to perform automatic updates.
/** @var \Drupal\automatic_updates\Validation\StatusCheckRequirements $status_check_requirement */ /** @var \Drupal\automatic_updates\Validation\StatusCheckRequirements $status_check_requirement */
$status_check_requirement = \Drupal::classResolver(StatusCheckRequirements::class); $status_check_requirement = \Drupal::classResolver(StatusCheckRequirements::class);
return $status_check_requirement->getRequirements(); $requirements = $status_check_requirement->getRequirements();
// Check that site has cron updates enabled or not.
// @todo Remove in https://www.drupal.org/project/automatic_updates/issues/3284443
if (\Drupal::configFactory()->get('automatic_updates.settings')->get('unattended.level') !== CronUpdateRunner::DISABLED) {
$requirements['automatic_updates_cron'] = [
'title' => t('Cron installs updates automatically'),
'severity' => SystemManager::REQUIREMENT_WARNING,
'value' => t('Enabled. This is NOT an officially supported feature of the Automatic Updates module at this time. Use at your own risk.'),
];
}
return $requirements;
} }
} }
// BEGIN: DELETE FROM CORE MERGE REQUEST
/** /**
* Stores cached readiness check results under a new key. * Implements hook_update_last_removed().
*/ */
function automatic_updates_update_9001(): void { function automatic_updates_update_last_removed(): int {
/** @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value */ return 9002;
$key_value = \Drupal::service('keyvalue.expirable')
->get('automatic_updates');
$key_value->rename('readiness_validation_last_run', 'status_check_last_run');
$key_value->rename('readiness_check_timestamp', 'status_check_timestamp');
} }
// END: DELETE FROM CORE MERGE REQUEST
...@@ -5,13 +5,20 @@ ...@@ -5,13 +5,20 @@
* Contains hook implementations for Automatic Updates. * Contains hook implementations for Automatic Updates.
*/ */
declare(strict_types = 1);
use Drupal\automatic_updates\BatchProcessor; use Drupal\automatic_updates\BatchProcessor;
use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\CronUpdateRunner;
use Drupal\automatic_updates\ReleaseChooser;
use Drupal\automatic_updates\UpdateStage;
use Drupal\automatic_updates\Validation\StatusChecker;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\automatic_updates\Validation\AdminStatusCheckMessages; use Drupal\automatic_updates\Validation\AdminStatusCheckMessages;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\package_manager\ComposerInspector;
use Drupal\Core\Utility\Error;
use Drupal\system\Controller\DbUpdateController; use Drupal\system\Controller\DbUpdateController;
use Drupal\package_manager\Validator\ComposerExecutableValidator;
/** /**
* Implements hook_help(). * Implements hook_help().
...@@ -29,12 +36,7 @@ function automatic_updates_help($route_name, RouteMatchInterface $route_match) { ...@@ -29,12 +36,7 @@ function automatic_updates_help($route_name, RouteMatchInterface $route_match) {
$output .= '</p>'; $output .= '</p>';
$output .= '<p>' . t('Additionally, Automatic Updates periodically runs checks to ensure that updates can be installed, and will warn site administrators if problems are detected.') . '</p>'; $output .= '<p>' . t('Additionally, Automatic Updates periodically runs checks to ensure that updates can be installed, and will warn site administrators if problems are detected.') . '</p>';
$output .= '<h3>' . t('Requirements') . '</h3>'; $output .= '<h3>' . t('Requirements') . '</h3>';
$output .= '<p>' . t('Automatic Updates requires a Composer executable whose version satisfies <code>@version</code>, and PHP must have permission to run it. The path to the executable may be set in the <code>package_manager.settings:executables.composer</code> config setting, or it will be automatically detected.', ['@version' => ComposerExecutableValidator::MINIMUM_COMPOSER_VERSION_CONSTRAINT]) . '</p>'; $output .= '<p>' . t('Automatic Updates requires a Composer executable whose version satisfies <code>@version</code>, and PHP must have permission to run it. The path to the executable may be set in the <code>package_manager.settings:executables.composer</code> config setting, or it will be automatically detected.', ['@version' => ComposerInspector::SUPPORTED_VERSION]) . '</p>';
$output .= '<p id="cron-alternate-port">' . t('If your site is running on the built-in PHP web server, unattended (i.e., cron) updates may not work without one of the following workarounds:') . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('Use a multithreaded web server, such as Apache, NGINX, or on Windows, IIS.') . '</li>';
$output .= '<li>' . t('Run another instance of the built-in PHP web server on a different port and configure automatic updates accordingly: <code>$config["automatic_updates.settings"]["cron_port"] = $alternate_port_number;</code>') . '</li>';
$output .= '</ul>';
$output .= '<p>' . t('For more information, see the <a href=":automatic-updates-documentation">online documentation for the Automatic Updates module</a>.', [':automatic-updates-documentation' => 'https://www.drupal.org/docs/8/update/automatic-updates']) . '</p>'; $output .= '<p>' . t('For more information, see the <a href=":automatic-updates-documentation">online documentation for the Automatic Updates module</a>.', [':automatic-updates-documentation' => 'https://www.drupal.org/docs/8/update/automatic-updates']) . '</p>';
$output .= '<h3 id="minor-update">' . t('Updating to another minor version of Drupal') . '</h3>'; $output .= '<h3 id="minor-update">' . t('Updating to another minor version of Drupal') . '</h3>';
$output .= '<p>'; $output .= '<p>';
...@@ -102,7 +104,7 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo ...@@ -102,7 +104,7 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo
$url = Url::fromRoute('system.status') $url = Url::fromRoute('system.status')
->setAbsolute() ->setAbsolute()
->toString(); ->toString();
$message['body'][] = t('Your site has failed some readiness checks for automatic updates and may not be able to receive automatic updates until further action is taken. Please visit @url for more information.', [ $message['body'][] = t('Your site has failed some readiness checks for automatic updates and may not be able to receive automatic updates until further action is taken. Visit @url for more information.', [
'@url' => $url, '@url' => $url,
], $options); ], $options);
} }
...@@ -110,7 +112,7 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo ...@@ -110,7 +112,7 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo
// If this email was related to an unattended update, explicitly state that // If this email was related to an unattended update, explicitly state that
// this isn't supported yet. // this isn't supported yet.
if (str_starts_with($key, 'cron_')) { if (str_starts_with($key, 'cron_')) {
$message['body'][] = t('This e-mail was sent by the Automatic Updates module. Unattended updates are not yet fully supported.', [], $options); $message['body'][] = t('This email was sent by the Automatic Updates module. Unattended updates are not yet fully supported.', [], $options);
$message['body'][] = t('If you are using this feature in production, it is strongly recommended for you to visit your site and ensure that everything still looks good.', [], $options); $message['body'][] = t('If you are using this feature in production, it is strongly recommended for you to visit your site and ensure that everything still looks good.', [], $options);
} }
} }
...@@ -118,17 +120,30 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo ...@@ -118,17 +120,30 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo
/** /**
* Implements hook_page_top(). * Implements hook_page_top().
*/ */
function automatic_updates_page_top() { function automatic_updates_page_top(array &$page_top) {
// Ensure error messages will be displayed on the batch page.
// @todo Remove this work around when https://drupal.org/i/3406612 is fixed.
if (\Drupal::routeMatch()->getRouteName() === 'system.batch_page.html') {
// Directly render a status message placeholder without any messages.
// Messages are not intended to be show on the batch page, but in the event
// an error in a AJAX callback the messages will be displayed.
$page_top['messages'] = [
'#theme' => 'status_messages',
'#message_list' => [],
'#status_headings' => [
'status' => t('Status message'),
'error' => t('Error message'),
'warning' => t('Warning message'),
],
];
}
/** @var \Drupal\automatic_updates\Validation\AdminStatusCheckMessages $status_check_messages */ /** @var \Drupal\automatic_updates\Validation\AdminStatusCheckMessages $status_check_messages */
$status_check_messages = \Drupal::classResolver(AdminStatusCheckMessages::class); $status_check_messages = \Drupal::classResolver(AdminStatusCheckMessages::class);
$status_check_messages->displayAdminPageMessages(); $status_check_messages->displayAdminPageMessages();
// @todo Rely on the route option after https://www.drupal.org/i/3236497 is // @todo Rely on the route option after https://www.drupal.org/i/3236497 is
// committed. // committed.
// @todo Remove 'system.batch_page.html' after
// https://www.drupal.org/i/3238311 is committed.
$skip_routes = [ $skip_routes = [
'system.batch_page.html',
'automatic_updates.confirmation_page', 'automatic_updates.confirmation_page',
'automatic_updates.report_update', 'automatic_updates.report_update',
'automatic_updates.module_update', 'automatic_updates.module_update',
...@@ -153,47 +168,27 @@ function automatic_updates_module_implements_alter(&$implementations, $hook) { ...@@ -153,47 +168,27 @@ function automatic_updates_module_implements_alter(&$implementations, $hook) {
// own routes to avoid these messages while an update is in progress. // own routes to avoid these messages while an update is in progress.
unset($implementations['update']); unset($implementations['update']);
} }
if ($hook === 'cron') {
$hook = $implementations['automatic_updates'];
unset($implementations['automatic_updates']);
$implementations['automatic_updates'] = $hook;
}
}
/**
* Implements hook_cron().
*/
function automatic_updates_cron() {
/** @var \Drupal\automatic_updates\CronUpdater $updater */
$updater = \Drupal::service('automatic_updates.cron_updater');
$updater->handleCron();
/** @var \Drupal\automatic_updates\Validation\StatusChecker $status_checker */
$status_checker = \Drupal::service('automatic_updates.status_checker');
$last_results = $status_checker->getResults();
$last_run_time = $status_checker->getLastRunTime();
// Do not run status checks more than once an hour unless there are no results
// available.
if ($last_results === NULL || !$last_run_time || \Drupal::time()->getRequestTime() - $last_run_time > 3600) {
$status_checker->run();
}
// Only try to send failure notifications if unattended updates are enabled.
if ($updater->getMode() !== CronUpdater::DISABLED) {
\Drupal::service('automatic_updates.status_check_mailer')
->sendFailureNotifications($last_results, $status_checker->getResults());
}
} }
/** /**
* Implements hook_modules_installed(). * Implements hook_modules_installed().
*/ */
function automatic_updates_modules_installed() { function automatic_updates_modules_installed($modules) {
// Run the status checkers if needed when any modules are installed in // Run the status checkers if needed when any modules are installed in
// case they provide status checkers. // case they provide status checkers.
/** @var \Drupal\automatic_updates\Validation\StatusChecker $status_checker */ /** @var \Drupal\automatic_updates\Validation\StatusChecker $status_checker */
$status_checker = \Drupal::service('automatic_updates.status_checker'); $status_checker = \Drupal::service(StatusChecker::class);
$status_checker->run(); $status_checker->run();
/** @var \Drupal\automatic_updates\CronUpdateRunner $runner */
$runner = \Drupal::service(CronUpdateRunner::class);
// If cron updates are disabled status check messages will not be displayed on
// admin pages. Therefore, after installing the module the user will not be
// alerted to any problems until they access the status report page.
if ($runner->getMode() === CronUpdateRunner::DISABLED) {
/** @var \Drupal\automatic_updates\Validation\AdminStatusCheckMessages $status_check_messages */
$status_check_messages = \Drupal::classResolver(AdminStatusCheckMessages::class);
$status_check_messages->displayResultSummary();
}
} }
/** /**
...@@ -203,7 +198,7 @@ function automatic_updates_modules_uninstalled() { ...@@ -203,7 +198,7 @@ function automatic_updates_modules_uninstalled() {
// Run the status checkers if needed when any modules are uninstalled in // Run the status checkers if needed when any modules are uninstalled in
// case they provided status checkers. // case they provided status checkers.
/** @var \Drupal\automatic_updates\Validation\StatusChecker $status_checker */ /** @var \Drupal\automatic_updates\Validation\StatusChecker $status_checker */
$status_checker = \Drupal::service('automatic_updates.status_checker'); $status_checker = \Drupal::service(StatusChecker::class);
$status_checker->run(); $status_checker->run();
} }
...@@ -220,6 +215,61 @@ function automatic_updates_batch_alter(array &$batch): void { ...@@ -220,6 +215,61 @@ function automatic_updates_batch_alter(array &$batch): void {
} }
} }
/**
* Implements hook_form_FORM_ID_alter() for update_settings.
*/
function automatic_updates_form_update_settings_alter(array &$form): void {
$config = \Drupal::config('automatic_updates.settings');
$form['unattended_level'] = [
'#type' => 'radios',
'#title' => t('Unattended background updates'),
'#options' => [
CronUpdateRunner::DISABLED => t('Disabled'),
CronUpdateRunner::SECURITY => t('Security updates only'),
CronUpdateRunner::ALL => t('All patch releases'),
],
'#default_value' => $config->get('unattended.level'),
'#description' => t('When background updates are applied, your site will be briefly put into maintenance mode.'),
];
$form['unattended_method'] = [
'#type' => 'radios',
'#title' => t('How unattended updates should be run'),
'#options' => [
'web' => t('By using the Automated Cron module or a request to /system/cron'),
'console' => t('By the <code>auto-update</code> command-line utility'),
],
'#default_value' => $config->get('unattended.method'),
'#states' => [
'invisible' => [
'input[name="unattended_level"]' => [
'value' => CronUpdateRunner::DISABLED,
],
],
],
'#description' => t('To use the <code>/system/cron</code> method <a href="http://drupal.org/docs/user_guide/en/security-cron.html">ensure cron is set up correctly</a>.'),
];
$form['#submit'][] = '_automatic_updates_submit_update_settings';
}
/**
* Saves settings for unattended updates.
*
* @param array $form
* The complete form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
function _automatic_updates_submit_update_settings(array &$form, FormStateInterface $form_state): void {
\Drupal::configFactory()
->getEditable('automatic_updates.settings')
->set('unattended', [
'method' => $form_state->getValue('unattended_method'),
'level' => $form_state->getValue('unattended_level'),
])
->save();
}
/** /**
* Implements hook_preprocess_update_project_status(). * Implements hook_preprocess_update_project_status().
*/ */
...@@ -228,22 +278,22 @@ function automatic_updates_preprocess_update_project_status(array &$variables) { ...@@ -228,22 +278,22 @@ function automatic_updates_preprocess_update_project_status(array &$variables) {
if ($project['name'] !== 'drupal') { if ($project['name'] !== 'drupal') {
return; return;
} }
$updater = \Drupal::service('automatic_updates.updater'); $stage = \Drupal::service(UpdateStage::class);
$supported_target_versions = []; $supported_target_versions = [];
/** @var \Drupal\automatic_updates\ReleaseChooser $recommender */ /** @var \Drupal\automatic_updates\ReleaseChooser $recommender */
$recommender = \Drupal::service('automatic_updates.release_chooser'); $recommender = \Drupal::service(ReleaseChooser::class);
try { try {
if ($installed_minor_release = $recommender->getLatestInInstalledMinor($updater)) { if ($installed_minor_release = $recommender->getLatestInInstalledMinor($stage)) {
$supported_target_versions[] = $installed_minor_release->getVersion(); $supported_target_versions[] = $installed_minor_release->getVersion();
} }
if ($next_minor_release = $recommender->getLatestInNextMinor($updater)) { if ($next_minor_release = $recommender->getLatestInNextMinor($stage)) {
$supported_target_versions[] = $next_minor_release->getVersion(); $supported_target_versions[] = $next_minor_release->getVersion();
} }
} }
catch (RuntimeException $exception) { catch (RuntimeException $exception) {
// If for some reason we are not able to get the update recommendations // If for some reason we are not able to get the update recommendations
// do not alter the report. // do not alter the report.
watchdog_exception('automatic_updates', $exception); Error::logException(\Drupal::logger('automatic_updates'), $exception);
return; return;
} }
$variables['#attached']['library'][] = 'automatic_updates/update_status'; $variables['#attached']['library'][] = 'automatic_updates/update_status';
......
...@@ -3,16 +3,17 @@ ...@@ -3,16 +3,17 @@
/** /**
* @file * @file
* Contains post-update hooks for Automatic Updates. * Contains post-update hooks for Automatic Updates.
*
* DELETE THIS FILE FROM CORE MERGE REQUEST.
*/ */
use Drupal\automatic_updates\StatusCheckMailer; declare(strict_types = 1);
/** /**
* Creates the automatic_updates.settings:status_check_mail config. * Implements hook_removed_post_updates().
*/ */
function automatic_updates_post_update_create_status_check_mail_config(): void { function automatic_updates_removed_post_updates(): array {
\Drupal::configFactory() return [
->getEditable('automatic_updates.settings') 'automatic_updates_post_update_create_status_check_mail_config' => '3.0.0',
->set('status_check_mail', StatusCheckMailer::ERRORS_ONLY) ];
->save();
} }
...@@ -27,42 +27,3 @@ automatic_updates.finish: ...@@ -27,42 +27,3 @@ automatic_updates.finish:
options: options:
_maintenance_access: TRUE _maintenance_access: TRUE
_automatic_updates_status_messages: skip _automatic_updates_status_messages: skip
automatic_updates.cron.post_apply:
path: '/automatic-update/cron/post-apply/{stage_id}/{installed_version}/{target_version}/{key}'
defaults:
_controller: 'automatic_updates.cron_updater:handlePostApply'
requirements:
_access_system_cron: 'TRUE'
# These routes are deprecated and will be removed in the next major version of
# Automatic Updates. They redirect to existing routes from the core Update
# module, which are overridden by Automatic Updates.
# @see \Drupal\automatic_updates\Routing\RouteSubscriber::alterRoutes()
automatic_updates.report_update:
path: '/admin/reports/updates/automatic-update'
defaults:
_controller: '\Drupal\automatic_updates\Controller\UpdateController::redirectDeprecatedRoute'
requirements:
_access: 'TRUE'
automatic_updates.module_update:
path: '/admin/modules/automatic-update'
defaults:
_controller: '\Drupal\automatic_updates\Controller\UpdateController::redirectDeprecatedRoute'
requirements:
_access: 'TRUE'
automatic_updates.theme_update:
path: '/admin/theme/automatic-update'
defaults:
_controller: '\Drupal\automatic_updates\Controller\UpdateController::redirectDeprecatedRoute'
requirements:
_access: 'TRUE'
automatic_updates.update_readiness:
path: '/admin/automatic_updates/readiness'
defaults:
_controller: '\Drupal\automatic_updates\Controller\StatusCheckController::runReadiness'
_title: 'Update readiness checking'
requirements:
_permission: 'administer software updates'
options:
_maintenance_access: TRUE
_automatic_updates_status_messages: skip
services: services:
automatic_updates.route_subscriber: _defaults:
class: Drupal\automatic_updates\Routing\RouteSubscriber autowire: true
Drupal\automatic_updates\Routing\RouteSubscriber:
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.status_checker: Drupal\automatic_updates\Validation\StatusChecker:
class: Drupal\automatic_updates\Validation\StatusChecker
arguments: arguments:
- '@keyvalue.expirable' # @todo Remove this when https://drupal.org/i/3325557 lands.
- '@datetime.time' $key_value_expirable_factory: '@keyvalue.expirable'
- '@event_dispatcher' $resultsTimeToLive: 24
- '@automatic_updates.updater'
- '@automatic_updates.cron_updater'
- 24
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.status_check_mailer: Drupal\automatic_updates\StatusCheckMailer: ~
class: Drupal\automatic_updates\StatusCheckMailer Drupal\automatic_updates\UpdateStage:
arguments:
- '@config.factory'
- '@plugin.manager.mail'
- '@language_manager'
automatic_updates.readiness_validation_manager:
class: Drupal\automatic_updates\Validation\ReadinessValidationManager
arguments:
- '@automatic_updates.status_checker'
deprecated: The "%service_id%" service is deprecated in automatic_updates:8.x-2.5 and is removed from automatic_updates:3.0.0. Use the automatic_updates.status_checker service instead. See https://www.drupal.org/node/3316086.
automatic_updates.updater:
class: Drupal\automatic_updates\Updater
arguments:
- '@config.factory'
- '@package_manager.path_locator'
- '@package_manager.beginner'
- '@package_manager.stager'
- '@package_manager.committer'
- '@file_system'
- '@event_dispatcher'
- '@tempstore.shared'
- '@datetime.time'
- '@PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface'
- '@package_manager.failure_marker'
calls: calls:
- ['setLogger', ['@logger.channel.automatic_updates']] - ['setLogger', ['@logger.channel.automatic_updates']]
automatic_updates.cron_updater: Drupal\automatic_updates\CronUpdateRunner:
class: Drupal\automatic_updates\CronUpdater
arguments:
- '@automatic_updates.release_chooser'
- '@logger.factory'
- '@plugin.manager.mail'
- '@automatic_updates.status_check_mailer'
- '@state'
- '@config.factory'
- '@package_manager.path_locator'
- '@package_manager.beginner'
- '@package_manager.stager'
- '@package_manager.committer'
- '@file_system'
- '@event_dispatcher'
- '@tempstore.shared'
- '@datetime.time'
- '@PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface'
- '@package_manager.failure_marker'
calls: calls:
- ['setLogger', ['@logger.channel.automatic_updates']] - ['setLogger', ['@logger.channel.automatic_updates']]
automatic_updates.staged_projects_validator: decorates: 'cron'
class: Drupal\automatic_updates\Validator\StagedProjectsValidator Drupal\automatic_updates\Validator\RequestedUpdateValidator:
arguments:
- '@string_translation'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.release_chooser: Drupal\automatic_updates\Validator\StagedProjectsValidator:
class: Drupal\automatic_updates\ReleaseChooser
arguments:
- '@automatic_updates.validator.version_policy'
automatic_updates.cron_frequency_validator:
class: Drupal\automatic_updates\Validator\CronFrequencyValidator
arguments:
- '@config.factory'
- '@module_handler'
- '@state'
- '@datetime.time'
- '@string_translation'
- '@automatic_updates.cron_updater'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.validator.staged_database_updates: Drupal\automatic_updates\ReleaseChooser: ~
class: Drupal\automatic_updates\Validator\StagedDatabaseUpdateValidator Drupal\automatic_updates\Validator\CronFrequencyValidator:
arguments: arguments:
- '@package_manager.validator.staged_database_updates' $lock: '@lock'
- '@string_translation'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.validator.xdebug: Drupal\automatic_updates\Validator\StagedDatabaseUpdateValidator:
class: Drupal\automatic_updates\Validator\XdebugValidator
arguments:
- '@package_manager.validator.xdebug'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.validator.version_policy: Drupal\automatic_updates\Validator\VersionPolicyValidator:
class: Drupal\automatic_updates\Validator\VersionPolicyValidator
arguments:
- '@class_resolver'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.config_subscriber: Drupal\automatic_updates\Validator\WindowsValidator:
class: Drupal\automatic_updates\EventSubscriber\ConfigSubscriber
arguments:
- '@automatic_updates.status_checker'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.validator.scaffold_file_permissions: logger.channel.automatic_updates:
class: Drupal\automatic_updates\Validator\ScaffoldFilePermissionsValidator parent: logger.channel_base
arguments: ['automatic_updates']
Drupal\automatic_updates\ConsoleUpdateStage:
arguments: arguments:
- '@package_manager.path_locator' $lock: '@lock'
$committer: '@Drupal\automatic_updates\MaintenanceModeAwareCommitter'
calls:
- ['setLogger', ['@logger.channel.automatic_updates']]
Drupal\automatic_updates\MaintenanceModeAwareCommitter:
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.validator.cron_server: Drupal\automatic_updates\CommandExecutor:
class: Drupal\automatic_updates\Validator\CronServerValidator
arguments: arguments:
- '@request_stack' $appRoot: '%app.root%'
- '@config.factory' Drupal\automatic_updates\Validator\ConsoleUserValidator:
- '@module_handler'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
logger.channel.automatic_updates:
parent: logger.channel_base
arguments: ['automatic_updates']
name: 'Automatic Updates Extensions' name: 'Automatic Updates Extensions'
type: module type: module
description: 'Allows updates to themes and modules' description: 'Allows updates to themes and modules'
core_version_requirement: ^9.3 core_version_requirement: ^10.1
lifecycle: experimental
dependencies: dependencies:
- drupal:automatic_updates - drupal:automatic_updates
services: services:
automatic_updates_extensions.updater: _defaults:
class: Drupal\automatic_updates_extensions\ExtensionUpdater autowire: true
arguments:
- '@config.factory' Drupal\automatic_updates_extensions\ExtensionUpdateStage: ~
- '@package_manager.path_locator' Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator:
- '@package_manager.beginner' tags:
- '@package_manager.stager' - { name: event_subscriber }
- '@package_manager.committer' Drupal\automatic_updates_extensions\Validator\ForbidCoreChangesValidator:
- '@file_system' tags:
- '@event_dispatcher' - { name: event_subscriber }
- '@tempstore.shared' Drupal\automatic_updates_extensions\Validator\RequestedUpdateValidator:
- '@datetime.time'
- '@PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface'
- '@package_manager.failure_marker'
automatic_updates_extensions.validator.target_release:
class: Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
<?php <?php
declare(strict_types = 1);
namespace Drupal\automatic_updates_extensions; namespace Drupal\automatic_updates_extensions;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\package_manager\Exception\StageValidationException;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
/** /**
...@@ -23,13 +24,13 @@ final class BatchProcessor { ...@@ -23,13 +24,13 @@ final class BatchProcessor {
public const STAGE_ID_SESSION_KEY = '_automatic_updates_extensions_stage_id'; public const STAGE_ID_SESSION_KEY = '_automatic_updates_extensions_stage_id';
/** /**
* Gets the updater service. * Gets the update stage service.
* *
* @return \Drupal\automatic_updates_extensions\ExtensionUpdater * @return \Drupal\automatic_updates_extensions\ExtensionUpdateStage
* The updater service. * The update stage service.
*/ */
protected static function getUpdater(): ExtensionUpdater { private static function getStage(): ExtensionUpdateStage {
return \Drupal::service('automatic_updates_extensions.updater'); return \Drupal::service(ExtensionUpdateStage::class);
} }
/** /**
...@@ -44,40 +45,24 @@ final class BatchProcessor { ...@@ -44,40 +45,24 @@ final class BatchProcessor {
* The caught exception, which will always be re-thrown once its messages * The caught exception, which will always be re-thrown once its messages
* have been recorded. * have been recorded.
*/ */
protected static function handleException(\Throwable $error, array &$context): void { private static function handleException(\Throwable $error, array &$context): void {
$error_messages = [ $context['results']['errors'][] = $error->getMessage();
$error->getMessage(),
];
if ($error instanceof StageValidationException) {
foreach ($error->getResults() as $result) {
$messages = $result->getMessages();
if (count($messages) > 1) {
array_unshift($messages, $result->getSummary());
}
$error_messages = array_merge($error_messages, $messages);
}
}
foreach ($error_messages as $error_message) {
$context['results']['errors'][] = $error_message;
}
throw $error; throw $error;
} }
/** /**
* Calls the updater's begin() method. * Calls the update stage's begin() method.
* *
* @param string[] $project_versions * @param string[] $project_versions
* The project versions to be staged in the update, keyed by package name. * The project versions to be staged in the update, keyed by package name.
* @param array $context * @param array $context
* The current context of the batch job. * The current context of the batch job.
* *
* @see \Drupal\automatic_updates_extensions\ExtensionUpdater::begin() * @see \Drupal\automatic_updates_extensions\ExtensionUpdateStage::begin()
*/ */
public static function begin(array $project_versions, array &$context): void { public static function begin(array $project_versions, array &$context): void {
try { try {
$stage_id = static::getUpdater()->begin($project_versions); $stage_id = static::getStage()->begin($project_versions);
\Drupal::service('session')->set(static::STAGE_ID_SESSION_KEY, $stage_id); \Drupal::service('session')->set(static::STAGE_ID_SESSION_KEY, $stage_id);
} }
catch (\Throwable $e) { catch (\Throwable $e) {
...@@ -86,37 +71,41 @@ final class BatchProcessor { ...@@ -86,37 +71,41 @@ final class BatchProcessor {
} }
/** /**
* Calls the updater's stageVersions() method. * Calls the update stage's stage() method.
* *
* @param array $context * @param array $context
* The current context of the batch job. * The current context of the batch job.
* *
* @see \Drupal\automatic_updates\Updater::stage() * @see \Drupal\automatic_updates\UpdateStage::stage()
*/ */
public static function stage(array &$context): void { public static function stage(array &$context): void {
$stage_id = \Drupal::service('session')->get(static::STAGE_ID_SESSION_KEY);
try { try {
$stage_id = \Drupal::service('session')->get(static::STAGE_ID_SESSION_KEY); static::getStage()->claim($stage_id)->stage();
static::getUpdater()->claim($stage_id)->stage();
} }
catch (\Throwable $e) { catch (\Throwable $e) {
static::clean($stage_id, $context); // If the stage was not already destroyed because of this exception
// destroy it.
if (!static::getStage()->isAvailable()) {
static::clean($stage_id, $context);
}
static::handleException($e, $context); static::handleException($e, $context);
} }
} }
/** /**
* Calls the updater's commit() method. * Calls the update stage's apply() method.
* *
* @param string $stage_id * @param string $stage_id
* The stage ID. * The stage ID.
* @param array $context * @param array $context
* The current context of the batch job. * The current context of the batch job.
* *
* @see \Drupal\automatic_updates\Updater::apply() * @see \Drupal\automatic_updates_extensions\ExtensionUpdateStage::apply()
*/ */
public static function commit(string $stage_id, array &$context): void { public static function commit(string $stage_id, array &$context): void {
try { try {
static::getUpdater()->claim($stage_id)->apply(); static::getStage()->claim($stage_id)->apply();
// The batch system does not allow any single request to run for longer // The batch system does not allow any single request to run for longer
// than a second, so this will force the next operation to be done in a // than a second, so this will force the next operation to be done in a
// new request. This helps keep the running code in as consistent a state // new request. This helps keep the running code in as consistent a state
...@@ -133,18 +122,18 @@ final class BatchProcessor { ...@@ -133,18 +122,18 @@ final class BatchProcessor {
} }
/** /**
* Calls the updater's postApply() method. * Calls the update stage's postApply() method.
* *
* @param string $stage_id * @param string $stage_id
* The stage ID. * The stage ID.
* @param array $context * @param array $context
* The current context of the batch job. * The current context of the batch job.
* *
* @see \Drupal\automatic_updates\Updater::postApply() * @see \Drupal\automatic_updates\UpdateStage::postApply()
*/ */
public static function postApply(string $stage_id, array &$context): void { public static function postApply(string $stage_id, array &$context): void {
try { try {
static::getUpdater()->claim($stage_id)->postApply(); static::getStage()->claim($stage_id)->postApply();
} }
catch (\Throwable $e) { catch (\Throwable $e) {
static::handleException($e, $context); static::handleException($e, $context);
...@@ -152,18 +141,18 @@ final class BatchProcessor { ...@@ -152,18 +141,18 @@ final class BatchProcessor {
} }
/** /**
* Calls the updater's clean() method. * Calls the update stage's destroy() method.
* *
* @param string $stage_id * @param string $stage_id
* The stage ID. * The stage ID.
* @param array $context * @param array $context
* The current context of the batch job. * The current context of the batch job.
* *
* @see \Drupal\automatic_updates\Updater::clean() * @see \Drupal\automatic_updates_extensions\ExtensionUpdateStage::destroy()
*/ */
public static function clean(string $stage_id, array &$context): void { public static function clean(string $stage_id, array &$context): void {
try { try {
static::getUpdater()->claim($stage_id)->destroy(); static::getStage()->claim($stage_id)->destroy();
} }
catch (\Throwable $e) { catch (\Throwable $e) {
static::handleException($e, $context); static::handleException($e, $context);
...@@ -221,7 +210,7 @@ final class BatchProcessor { ...@@ -221,7 +210,7 @@ final class BatchProcessor {
* @param array $results * @param array $results
* The batch results. * The batch results.
*/ */
protected static function handleBatchError(array $results): void { private static function handleBatchError(array $results): void {
if (isset($results['errors'])) { if (isset($results['errors'])) {
foreach ($results['errors'] as $error) { foreach ($results['errors'] as $error) {
\Drupal::messenger()->addError($error); \Drupal::messenger()->addError($error);
......
<?php <?php
declare(strict_types = 1);
namespace Drupal\automatic_updates_extensions; namespace Drupal\automatic_updates_extensions;
use Drupal\automatic_updates\Exception\UpdateException; use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\Core\TempStore\SharedTempStoreFactory;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\FailureMarker;
use Drupal\package_manager\LegacyVersionUtility; use Drupal\package_manager\LegacyVersionUtility;
use Drupal\package_manager\Event\StageEvent; use Drupal\package_manager\PathLocator;
use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\StageBase;
use Drupal\package_manager\Stage; use PhpTuf\ComposerStager\API\Core\BeginnerInterface;
use PhpTuf\ComposerStager\API\Core\CommitterInterface;
use PhpTuf\ComposerStager\API\Core\StagerInterface;
use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/** /**
* Defines a service to perform updates for modules and themes. * Defines a service to perform updates for modules and themes.
...@@ -17,7 +26,54 @@ use Drupal\package_manager\Stage; ...@@ -17,7 +26,54 @@ use Drupal\package_manager\Stage;
* This class is an internal part of the module's update handling and * This class is an internal part of the module's update handling and
* should not be used by external code. * should not be used by external code.
*/ */
class ExtensionUpdater extends Stage { final class ExtensionUpdateStage extends StageBase {
/**
* {@inheritdoc}
*/
protected string $type = 'automatic_updates_extensions:attended';
/**
* Constructs a new ExtensionUpdateStage object.
*
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
* @param \PhpTuf\ComposerStager\API\Core\BeginnerInterface $beginner
* The beginner service.
* @param \PhpTuf\ComposerStager\API\Core\StagerInterface $stager
* The stager service.
* @param \PhpTuf\ComposerStager\API\Core\CommitterInterface $committer
* The committer service.
* @param \Drupal\Core\Queue\QueueFactory $queueFactory
* The queue factory.
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $eventDispatcher
* The event dispatcher service.
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempStoreFactory
* The shared tempstore factory.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface $pathFactory
* The path factory service.
* @param \Drupal\package_manager\FailureMarker $failureMarker
* The failure marker service.
*/
public function __construct(
protected readonly ComposerInspector $composerInspector,
PathLocator $pathLocator,
BeginnerInterface $beginner,
StagerInterface $stager,
CommitterInterface $committer,
QueueFactory $queueFactory,
EventDispatcherInterface $eventDispatcher,
SharedTempStoreFactory $tempStoreFactory,
TimeInterface $time,
PathFactoryInterface $pathFactory,
FailureMarker $failureMarker,
) {
parent::__construct($pathLocator, $beginner, $stager, $committer, $queueFactory, $eventDispatcher, $tempStoreFactory, $time, $pathFactory, $failureMarker);
}
/** /**
* Begins the update. * Begins the update.
...@@ -35,29 +91,27 @@ class ExtensionUpdater extends Stage { ...@@ -35,29 +91,27 @@ class ExtensionUpdater extends Stage {
if (empty($project_versions)) { if (empty($project_versions)) {
throw new \InvalidArgumentException("No projects to begin the update"); throw new \InvalidArgumentException("No projects to begin the update");
} }
$composer = $this->getActiveComposer();
$package_versions = [ $package_versions = [
'production' => [], 'production' => [],
'dev' => [], 'dev' => [],
]; ];
$require_dev = $composer->getComposer() $project_root = $this->pathLocator->getProjectRoot();
->getPackage() $info = $this->composerInspector->getRootPackageInfo($project_root);
->getDevRequires(); $installed_packages = $this->composerInspector->getInstalledPackagesList($project_root);
$installed_packages = $composer->getInstalledPackages();
foreach ($project_versions as $project_name => $version) { foreach ($project_versions as $project_name => $version) {
$package = $composer->getPackageForProject($project_name); $package = $installed_packages->getPackageByDrupalProjectName($project_name);
if (empty($package)) { if (empty($package)) {
throw new \InvalidArgumentException("The project $project_name is not a Drupal project known to Composer and cannot be updated."); throw new \InvalidArgumentException("The project $project_name is not a Drupal project known to Composer and cannot be updated.");
} }
// We don't support updating install profiles. // We don't support updating install profiles.
if ($installed_packages[$package]->getType() === 'drupal-profile') { if ($package->type === 'drupal-profile') {
throw new \InvalidArgumentException("The project $project_name cannot be updated because updating install profiles is not supported."); throw new \InvalidArgumentException("The project $project_name cannot be updated because updating install profiles is not supported.");
} }
$group = array_key_exists($package, $require_dev) ? 'dev' : 'production'; $group = isset($info['devRequires'][$package->name]) ? 'dev' : 'production';
$package_versions[$group][$package] = LegacyVersionUtility::convertToSemanticVersion($version); $package_versions[$group][$package->name] = LegacyVersionUtility::convertToSemanticVersion($version);
} }
// Ensure that package versions are available to pre-create event // Ensure that package versions are available to pre-create event
...@@ -100,30 +154,6 @@ class ExtensionUpdater extends Stage { ...@@ -100,30 +154,6 @@ class ExtensionUpdater extends Stage {
$this->require($versions['production'], $versions['dev']); $this->require($versions['production'], $versions['dev']);
} }
/**
* {@inheritdoc}
*/
protected function dispatch(StageEvent $event, callable $on_error = NULL): void {
try {
parent::dispatch($event, $on_error);
}
catch (StageValidationException $e) {
throw new UpdateException($e->getResults(), $e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function apply(?int $timeout = 600): void {
try {
parent::apply($timeout);
}
catch (ApplyFailedException $exception) {
throw new UpdateException([], 'The update operation failed to apply. The update may have been partially applied. It is recommended that the site be restored from a code backup.', $exception->getCode(), $exception);
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
<?php <?php
declare(strict_types = 1);
namespace Drupal\automatic_updates_extensions\Form; namespace Drupal\automatic_updates_extensions\Form;
use Drupal\automatic_updates\Form\UpdateFormBase; use Drupal\automatic_updates\Form\UpdateFormBase;
use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Exception\StageFailureMarkerException;
use Drupal\package_manager\InstalledPackage;
use Drupal\package_manager\PathLocator;
use Drupal\package_manager\ProjectInfo; use Drupal\package_manager\ProjectInfo;
use Drupal\package_manager\ValidationResult; use Drupal\package_manager\ValidationResult;
use Drupal\automatic_updates_extensions\BatchProcessor; use Drupal\automatic_updates_extensions\BatchProcessor;
use Drupal\automatic_updates\BatchProcessor as AutoUpdatesBatchProcessor; use Drupal\automatic_updates\BatchProcessor as AutoUpdatesBatchProcessor;
use Drupal\automatic_updates_extensions\ExtensionUpdater; use Drupal\automatic_updates_extensions\ExtensionUpdateStage;
use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
...@@ -26,68 +31,41 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; ...@@ -26,68 +31,41 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
* Defines a form to commit staged updates. * Defines a form to commit staged updates.
* *
* @internal * @internal
* Form classes are internal. * Form classes are internal and should not be used by external code.
*/ */
final class UpdateReady extends UpdateFormBase { final class UpdateReady extends UpdateFormBase {
/**
* The updater service.
*
* @var \Drupal\automatic_updates_extensions\ExtensionUpdater
*/
protected $updater;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The module list service.
*
* @var \Drupal\Core\Extension\ModuleExtensionList
*/
protected $moduleList;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/** /**
* Constructs a new UpdateReady object. * Constructs a new UpdateReady object.
* *
* @param \Drupal\automatic_updates_extensions\ExtensionUpdater $updater * @param \Drupal\automatic_updates_extensions\ExtensionUpdateStage $stage
* The updater service. * The update stage service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger * @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service. * The messenger service.
* @param \Drupal\Core\State\StateInterface $state * @param \Drupal\Core\State\StateInterface $state
* The state service. * The state service.
* @param \Drupal\Core\Extension\ModuleExtensionList $module_list * @param \Drupal\Core\Extension\ModuleExtensionList $moduleList
* The module list service. * The module list service.
* @param \Drupal\Core\Render\RendererInterface $renderer * @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service. * The renderer service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
* Event dispatcher service. * Event dispatcher service.
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
*/ */
public function __construct(ExtensionUpdater $updater, MessengerInterface $messenger, StateInterface $state, ModuleExtensionList $module_list, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher) { public function __construct(
$this->updater = $updater; private readonly ExtensionUpdateStage $stage,
MessengerInterface $messenger,
private readonly StateInterface $state,
private readonly ModuleExtensionList $moduleList,
private readonly RendererInterface $renderer,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly ComposerInspector $composerInspector,
private readonly PathLocator $pathLocator,
) {
$this->setMessenger($messenger); $this->setMessenger($messenger);
$this->state = $state;
$this->moduleList = $module_list;
$this->renderer = $renderer;
$this->eventDispatcher = $event_dispatcher;
} }
/** /**
...@@ -102,12 +80,14 @@ final class UpdateReady extends UpdateFormBase { ...@@ -102,12 +80,14 @@ final class UpdateReady extends UpdateFormBase {
*/ */
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('automatic_updates_extensions.updater'), $container->get(ExtensionUpdateStage::class),
$container->get('messenger'), $container->get('messenger'),
$container->get('state'), $container->get('state'),
$container->get('extension.list.module'), $container->get('extension.list.module'),
$container->get('renderer'), $container->get('renderer'),
$container->get('event_dispatcher') $container->get('event_dispatcher'),
$container->get(ComposerInspector::class),
$container->get(PathLocator::class),
); );
} }
...@@ -116,13 +96,13 @@ final class UpdateReady extends UpdateFormBase { ...@@ -116,13 +96,13 @@ final class UpdateReady extends UpdateFormBase {
*/ */
public function buildForm(array $form, FormStateInterface $form_state, string $stage_id = NULL) { public function buildForm(array $form, FormStateInterface $form_state, string $stage_id = NULL) {
try { try {
$this->updater->claim($stage_id); $this->stage->claim($stage_id);
} }
catch (StageOwnershipException $e) { catch (StageOwnershipException) {
$this->messenger()->addError($this->t('Cannot continue the update because another Composer operation is currently in progress.')); $this->messenger()->addError($this->t('Cannot continue the update because another Composer operation is currently in progress.'));
return $form; return $form;
} }
catch (ApplyFailedException $e) { catch (StageFailureMarkerException $e) {
$this->messenger()->addError($e->getMessage()); $this->messenger()->addError($e->getMessage());
return $form; return $form;
} }
...@@ -154,7 +134,11 @@ final class UpdateReady extends UpdateFormBase { ...@@ -154,7 +134,11 @@ final class UpdateReady extends UpdateFormBase {
$form['package_updates'] = $this->showUpdates(); $form['package_updates'] = $this->showUpdates();
$form['backup'] = [ $form['backup'] = [
'#prefix' => '<strong>', '#prefix' => '<strong>',
'#markup' => $this->t('Back up your database and site before you continue. <a href=":backup_url">Learn how</a>.', [':backup_url' => 'https://www.drupal.org/node/22281']), '#type' => 'checkbox',
'#title' => $this->t('Warning: Updating contributed modules or themes may leave your site inoperable or looking wrong.'),
'#description' => $this->t('Back up your database and site before you continue. <a href=":backup_url">Learn how</a>. Each contributed module or theme may follow different standards for backwards compatibility, may or may not have tests, and may add or remove features in any release. For these reasons, it is highly recommended that you test this update in a development environment first.', [':backup_url' => 'https://www.drupal.org/node/22281']),
'#required' => TRUE,
'#default_value' => FALSE,
'#suffix' => '</strong>', '#suffix' => '</strong>',
]; ];
$form['maintenance_mode'] = [ $form['maintenance_mode'] = [
...@@ -165,7 +149,7 @@ final class UpdateReady extends UpdateFormBase { ...@@ -165,7 +149,7 @@ final class UpdateReady extends UpdateFormBase {
// Don't run the status checks once the form has been submitted. // Don't run the status checks once the form has been submitted.
if (!$form_state->getUserInput()) { if (!$form_state->getUserInput()) {
$results = $this->runStatusCheck($this->updater, $this->eventDispatcher); $results = $this->runStatusCheck($this->stage, $this->eventDispatcher);
// This will have no effect if $results is empty. // This will have no effect if $results is empty.
$this->displayResults($results, $this->renderer); $this->displayResults($results, $this->renderer);
// If any errors occurred, return the form early so the user cannot // If any errors occurred, return the form early so the user cannot
...@@ -212,7 +196,7 @@ final class UpdateReady extends UpdateFormBase { ...@@ -212,7 +196,7 @@ final class UpdateReady extends UpdateFormBase {
*/ */
public function cancel(array &$form, FormStateInterface $form_state): void { public function cancel(array &$form, FormStateInterface $form_state): void {
try { try {
$this->updater->destroy(); $this->stage->destroy();
$this->messenger()->addStatus($this->t('The update was successfully cancelled.')); $this->messenger()->addStatus($this->t('The update was successfully cancelled.'));
$form_state->setRedirect('automatic_updates_extensions.report_update'); $form_state->setRedirect('automatic_updates_extensions.report_update');
} }
...@@ -228,18 +212,17 @@ final class UpdateReady extends UpdateFormBase { ...@@ -228,18 +212,17 @@ final class UpdateReady extends UpdateFormBase {
* A render array displaying packages that will be updated. * A render array displaying packages that will be updated.
*/ */
private function showUpdates(): array { private function showUpdates(): array {
// Get packages that were updated in the staging area. // Get packages that were updated in the stage directory.
$active = $this->updater->getActiveComposer(); $installed_packages = $this->composerInspector->getInstalledPackagesList($this->pathLocator->getProjectRoot());
$staged = $this->updater->getStageComposer(); $staged_packages = $this->composerInspector->getInstalledPackagesList($this->stage->getStageDirectory());
$updated_packages = $staged->getPackagesWithDifferentVersionsIn($active); $updated_packages = $staged_packages->getPackagesWithDifferentVersionsIn($installed_packages);
// Build a list of package names that were updated by user request. // Build a list of package names that were updated by user request.
$updated_by_request = []; $updated_by_request = [];
foreach ($this->updater->getPackageVersions() as $group) { foreach ($this->stage->getPackageVersions() as $group) {
$updated_by_request = array_merge($updated_by_request, array_keys($group)); $updated_by_request = array_merge($updated_by_request, array_keys($group));
} }
$installed_packages = $active->getInstalledPackages();
$updated_by_request_info = []; $updated_by_request_info = [];
$updated_project_info = []; $updated_project_info = [];
$supported_package_types = ['drupal-module', 'drupal-theme']; $supported_package_types = ['drupal-module', 'drupal-theme'];
...@@ -248,17 +231,17 @@ final class UpdateReady extends UpdateFormBase { ...@@ -248,17 +231,17 @@ final class UpdateReady extends UpdateFormBase {
// updated. // updated.
foreach ($updated_packages as $name => $updated_package) { foreach ($updated_packages as $name => $updated_package) {
// Ignore anything that isn't a module or a theme. // Ignore anything that isn't a module or a theme.
if (!in_array($updated_package->getType(), $supported_package_types, TRUE)) { if (!in_array($updated_package->type, $supported_package_types, TRUE)) {
continue; continue;
} }
$updated_project_info[$name] = [ $updated_project_info[$name] = [
'title' => $this->getProjectTitle($updated_package->getName()), 'title' => $this->getProjectTitleFromPackage($updated_package),
'installed_version' => $installed_packages[$name]->getPrettyVersion(), 'installed_version' => $installed_packages[$updated_package->name]->version,
'updated_version' => $updated_package->getPrettyVersion(), 'updated_version' => $updated_package->version,
]; ];
} }
foreach (array_keys($updated_packages) as $name) { foreach ($updated_packages as $name => $updated_package) {
// Sort the updated packages into two groups: the ones that were updated // Sort the updated packages into two groups: the ones that were updated
// at the request of the user, and the ones that got updated anyway // at the request of the user, and the ones that got updated anyway
// (probably due to Composer's dependency resolution). // (probably due to Composer's dependency resolution).
...@@ -284,21 +267,25 @@ final class UpdateReady extends UpdateFormBase { ...@@ -284,21 +267,25 @@ final class UpdateReady extends UpdateFormBase {
/** /**
* Gets the human-readable project title for a Composer package. * Gets the human-readable project title for a Composer package.
* *
* @param string $package_name * @param \Drupal\package_manager\InstalledPackage $package
* Package name. * The installed package.
* *
* @return string * @return string
* The human-readable title of the project. * The human-readable title of the project. If no project information is
* available, the package name is returned.
*/ */
private function getProjectTitle(string $package_name): string { private function getProjectTitleFromPackage(InstalledPackage $package): string {
$project_name = str_replace('drupal/', '', $package_name); $project_name = $package->getProjectName();
if (!$project_name) {
return $package->name;
}
$project_info = new ProjectInfo($project_name); $project_info = new ProjectInfo($project_name);
$project_data = $project_info->getProjectInfo(); $project_data = $project_info->getProjectInfo();
if ($project_data) { if ($project_data) {
return $project_data['title']; return $project_data['title'];
} }
else { else {
return $project_name; return $package->name;
} }
} }
......
<?php <?php
declare(strict_types = 1);
namespace Drupal\automatic_updates_extensions\Form; namespace Drupal\automatic_updates_extensions\Form;
use Drupal\automatic_updates\Form\UpdateFormBase; use Drupal\automatic_updates\Form\UpdateFormBase;
use Drupal\automatic_updates_extensions\BatchProcessor; use Drupal\automatic_updates_extensions\BatchProcessor;
use Drupal\automatic_updates_extensions\ExtensionUpdater; use Drupal\automatic_updates_extensions\ExtensionUpdateStage;
use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\State\StateInterface; use Drupal\Core\State\StateInterface;
use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Exception\StageFailureMarkerException;
use Drupal\package_manager\FailureMarker; use Drupal\package_manager\FailureMarker;
use Drupal\package_manager\PathLocator;
use Drupal\package_manager\ProjectInfo; use Drupal\package_manager\ProjectInfo;
use Drupal\package_manager\ValidationResult; use Drupal\package_manager\ValidationResult;
use Drupal\system\SystemManager; use Drupal\system\SystemManager;
use Drupal\update\ProjectRelease;
use Drupal\update\UpdateManagerInterface; use Drupal\update\UpdateManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
...@@ -24,79 +29,52 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; ...@@ -24,79 +29,52 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
* A form for selecting extension updates. * A form for selecting extension updates.
* *
* @internal * @internal
* Form classes are internal. * Form classes are internal and should not be used by external code.
*/ */
final class UpdaterForm extends UpdateFormBase { final class UpdaterForm extends UpdateFormBase {
/**
* The extension updater service.
*
* @var \Drupal\automatic_updates_extensions\ExtensionUpdater
*/
private $extensionUpdater;
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $eventDispatcher;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
private $state;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
private $renderer;
/**
* Failure marker service.
*
* @var \Drupal\package_manager\FailureMarker
*/
private $failureMarker;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('automatic_updates_extensions.updater'), $container->get(ExtensionUpdateStage::class),
$container->get('event_dispatcher'), $container->get('event_dispatcher'),
$container->get('renderer'), $container->get('renderer'),
$container->get('state'), $container->get('state'),
$container->get('package_manager.failure_marker') $container->get(FailureMarker::class),
$container->get(ComposerInspector::class),
$container->get(PathLocator::class),
); );
} }
/** /**
* Constructs a new UpdaterForm object. * Constructs a new UpdaterForm object.
* *
* @param \Drupal\automatic_updates_extensions\ExtensionUpdater $extension_updater * @param \Drupal\automatic_updates_extensions\ExtensionUpdateStage $stage
* The extension updater service. * The extension update stage service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
* The extension event dispatcher service. * The extension event dispatcher service.
* @param \Drupal\Core\Render\RendererInterface $renderer * @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service. * The renderer service.
* @param \Drupal\Core\State\StateInterface $state * @param \Drupal\Core\State\StateInterface $state
* The state service. * The state service.
* @param \Drupal\package_manager\FailureMarker $failure_marker * @param \Drupal\package_manager\FailureMarker $failureMarker
* The failure marker service. * The failure marker service.
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
*/ */
public function __construct(ExtensionUpdater $extension_updater, EventDispatcherInterface $event_dispatcher, RendererInterface $renderer, StateInterface $state, FailureMarker $failure_marker) { public function __construct(
$this->extensionUpdater = $extension_updater; private readonly ExtensionUpdateStage $stage,
$this->eventDispatcher = $event_dispatcher; private readonly EventDispatcherInterface $eventDispatcher,
$this->renderer = $renderer; private readonly RendererInterface $renderer,
$this->state = $state; private readonly StateInterface $state,
$this->failureMarker = $failure_marker; private readonly FailureMarker $failureMarker,
} private readonly ComposerInspector $composerInspector,
private readonly PathLocator $pathLocator,
) {}
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -112,7 +90,7 @@ final class UpdaterForm extends UpdateFormBase { ...@@ -112,7 +90,7 @@ final class UpdaterForm extends UpdateFormBase {
try { try {
$this->failureMarker->assertNotExists(); $this->failureMarker->assertNotExists();
} }
catch (ApplyFailedException $e) { catch (StageFailureMarkerException $e) {
$this->messenger()->addError($e->getMessage()); $this->messenger()->addError($e->getMessage());
return $form; return $form;
} }
...@@ -133,10 +111,16 @@ final class UpdaterForm extends UpdateFormBase { ...@@ -133,10 +111,16 @@ final class UpdaterForm extends UpdateFormBase {
default: default:
$status_message = ''; $status_message = '';
} }
$project_release = ProjectRelease::createFromArray($update_project['releases'][$update_project['recommended']]);
$options[$project_name] = [ $options[$project_name] = [
$update_project['title'] . $status_message, $update_project['title'] . $status_message,
$update_project['existing_version'], $update_project['existing_version'],
$update_project['recommended'], $this->t(
'@version (<a href=":url">Release notes</a>)',
[
'@version' => $project_release->getVersion(),
':url' => $project_release->getReleaseUrl(),
]),
]; ];
$recommended_versions[$project_name] = $update_project['recommended']; $recommended_versions[$project_name] = $update_project['recommended'];
} }
...@@ -155,14 +139,14 @@ final class UpdaterForm extends UpdateFormBase { ...@@ -155,14 +139,14 @@ final class UpdaterForm extends UpdateFormBase {
'#empty' => $this->t('There are no available updates.'), '#empty' => $this->t('There are no available updates.'),
'#attributes' => ['class' => ['update-recommended']], '#attributes' => ['class' => ['update-recommended']],
'#required' => TRUE, '#required' => TRUE,
'#required_error' => t('Please select one or more projects.'), '#required_error' => t('Select one or more projects.'),
]; ];
if ($form_state->getUserInput()) { if ($form_state->getUserInput()) {
$results = []; $results = [];
} }
else { else {
$results = $this->runStatusCheck($this->extensionUpdater, $this->eventDispatcher, TRUE); $results = $this->runStatusCheck($this->stage, $this->eventDispatcher);
} }
$this->displayResults($results, $this->renderer); $this->displayResults($results, $this->renderer);
$security_level = ValidationResult::getOverallSeverity($results); $security_level = ValidationResult::getOverallSeverity($results);
...@@ -187,9 +171,9 @@ final class UpdaterForm extends UpdateFormBase { ...@@ -187,9 +171,9 @@ final class UpdaterForm extends UpdateFormBase {
* @return mixed[][] * @return mixed[][]
* The form's actions elements. * The form's actions elements.
*/ */
protected function actions(FormStateInterface $form_state): array { private function actions(FormStateInterface $form_state): array {
$actions = ['#type' => 'actions']; $actions = ['#type' => 'actions'];
if (!$this->extensionUpdater->isAvailable()) { if (!$this->stage->isAvailable()) {
// If the form has been submitted do not display this error message // If the form has been submitted do not display this error message
// because ::deleteExistingUpdate() may run on submit. The message will // because ::deleteExistingUpdate() may run on submit. The message will
// still be displayed on form build if needed. // still be displayed on form build if needed.
...@@ -215,7 +199,7 @@ final class UpdaterForm extends UpdateFormBase { ...@@ -215,7 +199,7 @@ final class UpdaterForm extends UpdateFormBase {
* Submit function to delete an existing in-progress update. * Submit function to delete an existing in-progress update.
*/ */
public function deleteExistingUpdate(): void { public function deleteExistingUpdate(): void {
$this->extensionUpdater->destroy(TRUE); $this->stage->destroy(TRUE);
$this->messenger()->addMessage($this->t("Staged update deleted")); $this->messenger()->addMessage($this->t("Staged update deleted"));
} }
...@@ -259,12 +243,12 @@ final class UpdaterForm extends UpdateFormBase { ...@@ -259,12 +243,12 @@ final class UpdaterForm extends UpdateFormBase {
$all_projects_data = update_calculate_project_data($available_updates); $all_projects_data = update_calculate_project_data($available_updates);
$outdated_modules = []; $outdated_modules = [];
$installed_packages = array_keys($this->extensionUpdater->getActiveComposer()->getInstalledPackages()); $installed_packages = $this->composerInspector->getInstalledPackagesList($this->pathLocator->getProjectRoot());
$non_supported_update_statuses = []; $non_supported_update_statuses = [];
foreach ($all_projects_data as $project_name => $project_data) { foreach ($all_projects_data as $project_name => $project_data) {
if (in_array($project_data['project_type'], $supported_project_types, TRUE)) { if (in_array($project_data['project_type'], $supported_project_types, TRUE)) {
if ($project_data['status'] !== UpdateManagerInterface::CURRENT) { if ($project_data['status'] !== UpdateManagerInterface::CURRENT) {
if (!in_array("drupal/$project_name", $installed_packages, TRUE)) { if ($installed_packages->getPackageByDrupalProjectName($project_name) === NULL) {
$non_supported_update_statuses[] = $project_data['status']; $non_supported_update_statuses[] = $project_data['status'];
continue; continue;
} }
......
<?php
declare(strict_types=1);
namespace Drupal\automatic_updates_extensions\Validator;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\InstalledPackagesList;
use Drupal\package_manager\PathLocator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates that no changes were made to Drupal Core packages.
*
* @internal
* This is an internal part of Automatic Updates and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class ForbidCoreChangesValidator implements EventSubscriberInterface {
use StringTranslationTrait;
public function __construct(
private readonly PathLocator $pathLocator,
private readonly ComposerInspector $composerInspector,
) {}
/**
* Validates the staged packages.
*
* @param \Drupal\package_manager\Event\StatusCheckEvent|\Drupal\package_manager\Event\PreApplyEvent $event
* The event object.
*/
public function validateStagedCorePackages(StatusCheckEvent|PreApplyEvent $event): void {
$stage = $event->stage;
// We only want to do this check if the stage belongs to Automatic Updates
// Extensions.
if ($stage->getType() !== 'automatic_updates_extensions:attended' || !$stage->stageDirectoryExists()) {
return;
}
$active_core_packages = $this->getInstalledCorePackages($this->pathLocator->getProjectRoot());
$stage_core_packages = $this->getInstalledCorePackages($stage->getStageDirectory());
$new_packages = $stage_core_packages->getPackagesNotIn($active_core_packages);
$removed_packages = $active_core_packages->getPackagesNotIn($stage_core_packages);
$changed_packages = $active_core_packages->getPackagesWithDifferentVersionsIn($stage_core_packages);
$error_messages = [];
foreach ($new_packages as $new_package) {
$error_messages[] = $this->t("'@name' installed.", ['@name' => $new_package->name]);
}
foreach ($removed_packages as $removed_package) {
$error_messages[] = $this->t("'@name' removed.", ['@name' => $removed_package->name]);
}
foreach ($changed_packages as $name => $updated_package) {
$error_messages[] = $this->t(
"'@name' version changed from @active_version to @staged_version.",
[
'@name' => $updated_package->name,
'@staged_version' => $stage_core_packages[$name]->version,
'@active_version' => $updated_package->version,
]
);
}
if ($error_messages) {
$event->addError($error_messages, $this->t(
'Updating Drupal Core while updating extensions is currently not supported. Use <a href=":url">this form</a> to update Drupal core. The following changes were made to the Drupal core packages:',
[':url' => Url::fromRoute('update.report_update')->toString()]
));
}
}
/**
* Gets all the installed core packages for a given project root.
*
* This method differs from
* \Drupal\package_manager\ComposerInspector::getInstalledPackagesList in that
* it ensures that the 'drupal/core' is included in the list if present.
*
* @param string $composer_root
* The path to the composer root.
*
* @return \Drupal\package_manager\InstalledPackagesList
* The installed core packages.
*/
private function getInstalledCorePackages(string $composer_root): InstalledPackagesList {
$installed_package_list = $this->composerInspector->getInstalledPackagesList($composer_root);
$core_packages = $installed_package_list->getCorePackages();
if (isset($installed_package_list['drupal/core']) && !isset($core_packages['drupal/core'])) {
$core_packages = new InstalledPackagesList(array_merge($core_packages->getArrayCopy(), ['drupal/core' => $installed_package_list['drupal/core']]));
}
return $core_packages;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[StatusCheckEvent::class][] = ['validateStagedCorePackages'];
$events[PreApplyEvent::class][] = ['validateStagedCorePackages'];
return $events;
}
}
<?php
declare(strict_types=1);
namespace Drupal\automatic_updates_extensions\Validator;
use Composer\Semver\Semver;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\PathLocator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates that requested packages have been updated.
*
* @internal
* This is an internal part of Automatic Updates and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class RequestedUpdateValidator implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Constructs a RequestedUpdateValidator object.
*
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
*/
public function __construct(
private readonly ComposerInspector $composerInspector,
private readonly PathLocator $pathLocator,
) {}
/**
* Validates that requested packages have been updated to the right version.
*
* @param \Drupal\package_manager\Event\PreApplyEvent|\Drupal\package_manager\Event\StatusCheckEvent $event
* The pre-apply event.
*/
public function checkRequestedStagedVersion(PreApplyEvent|StatusCheckEvent $event): void {
$stage = $event->stage;
if ($stage->getType() !== 'automatic_updates_extensions:attended' || !$stage->stageDirectoryExists()) {
return;
}
$requested_package_versions = $stage->getPackageVersions();
$active = $this->composerInspector->getInstalledPackagesList($this->pathLocator->getProjectRoot());
$staged = $this->composerInspector->getInstalledPackagesList($event->stage->getStageDirectory());
$changed_stage_packages = $staged->getPackagesWithDifferentVersionsIn($active)->getArrayCopy();
if (empty($changed_stage_packages)) {
$event->addError([$this->t('No updates detected in the staging area.')]);
return;
}
// Check for all changed the packages if they are updated to the requested
// version.
foreach (['production', 'dev'] as $package_type) {
foreach ($requested_package_versions[$package_type] as $requested_package_name => $requested_version) {
if (array_key_exists($requested_package_name, $changed_stage_packages)) {
$staged_version = $changed_stage_packages[$requested_package_name]->version;
if (!Semver::satisfies($staged_version, $requested_version)) {
$event->addError([
$this->t(
"The requested update to '@requested_package_name' to version '@requested_version' does not match the actual staged update to '@staged_version'.",
[
'@requested_package_name' => $requested_package_name,
'@requested_version' => $requested_version,
'@staged_version' => $staged_version,
]
),
]);
}
}
else {
$event->addError([
$this->t(
"The requested update to '@requested_package_name' to version '@requested_version' was not performed.",
[
'@requested_package_name' => $requested_package_name,
'@requested_version' => $requested_version,
]
),
]);
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[StatusCheckEvent::class][] = ['checkRequestedStagedVersion'];
$events[PreApplyEvent::class][] = ['checkRequestedStagedVersion'];
return $events;
}
}
<?php <?php
declare(strict_types = 1);
namespace Drupal\automatic_updates_extensions\Validator; namespace Drupal\automatic_updates_extensions\Validator;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\PathLocator;
use Drupal\package_manager\ProjectInfo; use Drupal\package_manager\ProjectInfo;
use Drupal\automatic_updates_extensions\ExtensionUpdater;
use Drupal\package_manager\LegacyVersionUtility; use Drupal\package_manager\LegacyVersionUtility;
use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreCreateEvent;
...@@ -16,12 +19,26 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; ...@@ -16,12 +19,26 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
* This class is an internal part of the module's update handling and * This class is an internal part of the module's update handling and
* should not be used by external code. * should not be used by external code.
* *
* @todo Remove this validator completely in https://www.drupal.org/i/3307369. * @todo Decide if this validator can be removed completely in
* https://www.drupal.org/i/3351091.
*/ */
final class UpdateReleaseValidator implements EventSubscriberInterface { final class UpdateReleaseValidator implements EventSubscriberInterface {
use StringTranslationTrait; use StringTranslationTrait;
/**
* Constructs an UpdateReleaseValidator object.
*
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
*/
public function __construct(
private readonly ComposerInspector $composerInspector,
private readonly PathLocator $pathLocator
) {}
/** /**
* Checks if the given version of a project is supported. * Checks if the given version of a project is supported.
* *
...@@ -39,7 +56,7 @@ final class UpdateReleaseValidator implements EventSubscriberInterface { ...@@ -39,7 +56,7 @@ final class UpdateReleaseValidator implements EventSubscriberInterface {
* TRUE if the given version of the project is supported, otherwise FALSE. * TRUE if the given version of the project is supported, otherwise FALSE.
* given version is not supported will return FALSE. * given version is not supported will return FALSE.
*/ */
protected function isSupportedRelease(string $name, string $semantic_version): bool { private function isSupportedRelease(string $name, string $semantic_version): bool {
$supported_releases = (new ProjectInfo($name))->getInstallableReleases(); $supported_releases = (new ProjectInfo($name))->getInstallableReleases();
if (!$supported_releases) { if (!$supported_releases) {
return FALSE; return FALSE;
...@@ -69,17 +86,18 @@ final class UpdateReleaseValidator implements EventSubscriberInterface { ...@@ -69,17 +86,18 @@ final class UpdateReleaseValidator implements EventSubscriberInterface {
* The event object. * The event object.
*/ */
public function checkRelease(PreCreateEvent $event): void { public function checkRelease(PreCreateEvent $event): void {
$stage = $event->getStage(); $stage = $event->stage;
// This check only works with Automatic Updates Extensions. // This check only works with Automatic Updates Extensions.
if (!$stage instanceof ExtensionUpdater) { if ($stage->getType() !== 'automatic_updates_extensions:attended') {
return; return;
} }
$active_packages = $this->composerInspector->getInstalledPackagesList($this->pathLocator->getProjectRoot());
$all_versions = $stage->getPackageVersions(); $all_versions = $stage->getPackageVersions();
$messages = []; $messages = [];
foreach (['production', 'dev'] as $package_type) { foreach (['production', 'dev'] as $package_type) {
foreach ($all_versions[$package_type] as $package_name => $sematic_version) { foreach ($all_versions[$package_type] as $package_name => $sematic_version) {
$project_name = $stage->getActiveComposer()->getProjectForPackage($package_name); $project_name = $active_packages[$package_name]->getProjectName();
// If the version isn't in the list of installable releases, then it // If the version isn't in the list of installable releases, then it
// isn't secure and supported and the user should receive an error. // isn't secure and supported and the user should receive an error.
if (!$this->isSupportedRelease($project_name, $sematic_version)) { if (!$this->isSupportedRelease($project_name, $sematic_version)) {
......