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 (9)
Showing
with 786 additions and 199 deletions
......@@ -6,9 +6,9 @@
*/
use Drupal\automatic_updates\BatchProcessor;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\UpdateRecommender;
use Drupal\automatic_updates\Validation\AdminReadinessMessages;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\Form\FormStateInterface;
......@@ -20,12 +20,19 @@ use Drupal\update\ProjectSecurityData;
* Implements hook_help().
*/
function automatic_updates_help($route_name, RouteMatchInterface $route_match) {
// @todo Fully document all the modules features in
// https://www.drupal.org/i/3253591.
switch ($route_name) {
case 'help.page.auto_updates':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Automatic Updates lets you update Drupal core.') . '</p>';
$output .= '<p>';
$output .= t('Automatic Updates will keep Drupal secure and up-to-date by automatically installing new patch-level updates, if available, when cron runs. It also provides a user interface to check if any updates are available and install them. You can <a href=":configure-form">configure Automatic Updates</a> to install all patch-level updates, only security updates, or no updates at all, during cron. By default, only security updates are installed during cron; this requires that you <a href=":update-form">install non-security updates through the user interface</a>.', [
':configure-form' => Url::fromRoute('update.settings')->toString(),
':update-form' => Url::fromRoute('automatic_updates.report_update')->toString(),
]);
$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 .= '<h3>' . t('Requirements') . '</h3>';
$output .= '<p>' . t('Automatic Updates requires Composer @version or later available as an executable, 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]) . '</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>';
return $output;
}
......@@ -141,9 +148,8 @@ function automatic_updates_form_update_manager_update_form_alter(&$form, FormSta
* Implements hook_form_FORM_ID_alter() for 'update_settings' form.
*/
function automatic_updates_form_update_settings_alter(array &$form, FormStateInterface $form_state, string $form_id) {
$recommender = new UpdateRecommender();
$drupal_project = $recommender->getProjectInfo();
$version = ExtensionVersion::createFromVersionString($drupal_project['existing_version']);
$project_info = new ProjectInfo();
$version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion());
$current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion();
// @todo In https://www.drupal.org/node/2998285 use the update XML to
// determine when the installed of core will become unsupported.
......
......@@ -14,7 +14,6 @@ automatic_updates.confirmation_page:
_title: 'Ready to update'
requirements:
_permission: 'administer software updates'
_access_update_manager: 'TRUE'
options:
_maintenance_access: TRUE
automatic_updates.finish:
......@@ -37,6 +36,7 @@ automatic_updates.report_update:
options:
_admin_route: TRUE
_maintenance_access: TRUE
_automatic_updates_readiness_messages: skip
automatic_updates.module_update:
path: '/admin/modules/automatic-update'
defaults:
......@@ -47,6 +47,7 @@ automatic_updates.module_update:
options:
_admin_route: TRUE
_maintenance_access: TRUE
_automatic_updates_readiness_messages: skip
automatic_updates.theme_update:
path: '/admin/theme/automatic-update'
defaults:
......@@ -57,3 +58,4 @@ automatic_updates.theme_update:
options:
_admin_route: TRUE
_maintenance_access: TRUE
_automatic_updates_readiness_messages: skip
services:
automatic_updates.route_subscriber:
class: Drupal\automatic_updates\Routing\RouteSubscriber
tags:
- { name: event_subscriber }
automatic_updates.readiness_validation_manager:
class: Drupal\automatic_updates\Validation\ReadinessValidationManager
arguments:
......@@ -26,6 +30,7 @@ services:
automatic_updates.cron_updater:
class: Drupal\automatic_updates\CronUpdater
arguments:
- '@automatic_updates.cron_release_chooser'
- '@logger.factory'
- '@config.factory'
- '@package_manager.path_locator'
......@@ -55,6 +60,21 @@ services:
- '@config.factory'
tags:
- { name: event_subscriber }
automatic_updates.cron_update_version_validator:
class: Drupal\automatic_updates\Validator\CronUpdateVersionValidator
arguments:
- '@string_translation'
- '@config.factory'
tags:
- { name: event_subscriber }
automatic_updates.release_chooser:
class: Drupal\automatic_updates\ReleaseChooser
arguments:
- '@automatic_updates.update_version_validator'
automatic_updates.cron_release_chooser:
class: Drupal\automatic_updates\ReleaseChooser
arguments:
- '@automatic_updates.cron_update_version_validator'
automatic_updates.composer_executable_validator:
class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck
arguments:
......
......@@ -7,3 +7,4 @@ automatic_updates_extensions.update:
_permission: 'administer software updates'
options:
_admin_route: TRUE
_automatic_updates_readiness_messages: skip
......@@ -2,39 +2,71 @@
namespace Drupal\automatic_updates_extensions\Form;
use Drupal\automatic_updates\Updater;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates\Validation\ReadinessTrait;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\package_manager\Stage;
use Drupal\system\SystemManager;
use Drupal\update\UpdateManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* A form for selecting extension updates.
*/
class UpdaterForm extends FormBase {
use ReadinessTrait;
/**
* The updater service.
*
* @var \Drupal\automatic_updates\Updater
*/
private $updater;
private $stage;
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $eventDispatcher;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('automatic_updates.updater'));
// @todo Create a our servcie that extends stage instead of creating a
// generic stage class here.
$stage = new Stage(
$container->get('config.factory'),
$container->get('package_manager.path_locator'),
$container->get('package_manager.beginner'),
$container->get('package_manager.stager'),
$container->get('package_manager.committer'),
$container->get('file_system'),
$container->get('event_dispatcher'),
$container->get('tempstore.shared'),
$container->get('datetime.time')
);
return new static(
$stage,
$container->get('event_dispatcher'),
);
}
/**
* Constructs a new UpdaterForm object.
*
* @param \Drupal\automatic_updates\Updater $updater
* The extension updater service.
* @param \Drupal\package_manager\Stage $stage
* The stage service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The extension event dispatcher service.
*/
public function __construct(Updater $updater) {
$this->updater = $updater;
public function __construct(Stage $stage, EventDispatcherInterface $event_dispatcher) {
$this->stage = $stage;
$this->eventDispatcher = $event_dispatcher;
}
/**
......@@ -87,9 +119,22 @@ class UpdaterForm extends FormBase {
'#empty' => $this->t('There are no available updates.'),
'#attributes' => ['class' => ['update-recommended']],
];
if ($update_projects) {
if ($form_state->getUserInput()) {
$results = [];
}
else {
$event = new ReadinessCheckEvent($this->stage);
$this->eventDispatcher->dispatch($event);
$results = $event->getResults();
}
$this->displayResults($results, $this->messenger());
$security_level = $this->getOverallSeverity($results);
if ($update_projects && $security_level !== SystemManager::REQUIREMENT_ERROR) {
$form['actions'] = $this->actions($form_state);
}
return $form;
}
......@@ -104,7 +149,7 @@ class UpdaterForm extends FormBase {
*/
protected function actions(FormStateInterface $form_state): array {
$actions = ['#type' => 'actions'];
if (!$this->updater->isAvailable()) {
if (!$this->stage->isAvailable()) {
// If the form has been submitted do not display this error message
// because ::deleteExistingUpdate() may run on submit. The message will
// still be displayed on form build if needed.
......@@ -130,7 +175,7 @@ class UpdaterForm extends FormBase {
* Submit function to delete an existing in-progress update.
*/
public function deleteExistingUpdate(): void {
$this->updater->destroy(TRUE);
$this->stage->destroy(TRUE);
$this->messenger()->addMessage($this->t("Staged update deleted"));
}
......
......@@ -2,7 +2,11 @@
namespace Drupal\Tests\automatic_updates_extensions\Functional;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
use Drupal\package_manager\ValidationResult;
use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
/**
* Tests updating using the form.
......@@ -11,6 +15,8 @@ use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase
*/
class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
use ValidationTestTrait;
/**
* {@inheritdoc}
*/
......@@ -35,7 +41,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected function setUp():void {
protected function setUp(): void {
parent::setUp();
$this->setReleaseMetadata(__DIR__ . '/../../../../tests/fixtures/release-history/semver_test.1.1.xml');
}
......@@ -63,13 +69,26 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
'hidden' => FALSE,
],
];
$this->config('update_test.settings')->set('system_info', $system_info)->save();
$this->config('update_test.settings')
->set('system_info', $system_info)
->save();
}
/**
* Asserts the table shows the updates.
*/
private function assertTableShowsUpdates() {
$assert = $this->assertSession();
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(2)', 'Semver Test');
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(3)', '8.1.0');
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(4)', '8.1.1');
$assert->elementsCount('css', '.update-recommended tbody tr', 1);
}
/**
* Tests the form when a module requires an update.
*/
public function testHasUpdate():void {
public function testHasUpdate(): void {
$assert = $this->assertSession();
$user = $this->createUser(['administer site configuration']);
$this->drupalLogin($user);
......@@ -81,20 +100,18 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$user = $this->createUser(['administer software updates']);
$this->drupalLogin($user);
$this->drupalGet('/admin/automatic-update-extensions');
$this->assertTableShowsUpdates();
$assert->pageTextContains('Automatic Updates Form');
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(2)', 'Semver Test');
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(3)', '8.1.0');
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(4)', '8.1.1');
$assert->elementsCount('css', '.update-recommended tbody tr', 1);
$assert->buttonExists('Update');
}
/**
* Tests the form when there are no available updates.
*/
public function testNoUpdate():void {
public function testNoUpdate(): void {
$assert = $this->assertSession();
$user = $this->createUser(['administer site configuration',
$user = $this->createUser([
'administer site configuration',
'administer software updates',
]);
$this->drupalLogin($user);
......@@ -105,4 +122,50 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert->buttonNotExists('Update');
}
/**
* Test the form for errors.
*/
public function testErrors(): void {
$assert = $this->assertSession();
$user = $this->createUser([
'administer site configuration',
'administer software updates',
]);
$this->drupalLogin($user);
$this->setProjectInstalledVersion('8.1.0');
$this->checkForUpdates();
$this->drupalGet('/admin/automatic-update-extensions');
$this->assertTableShowsUpdates();
$message = t("You've not experienced Shakespeare until you have read him in the original Klingon.");
$error = ValidationResult::createError([$message]);
TestSubscriber1::setTestResult([$error], ReadinessCheckEvent::class);
$this->getSession()->reload();
$assert->pageTextContains($message);
$assert->pageTextContains(static::$errorsExplanation);
$assert->pageTextNotContains(static::$warningsExplanation);
$assert->buttonNotExists('Update');
}
/**
* Test the form for warning messages.
*/
public function testWarnings(): void {
$assert = $this->assertSession();
$user = $this->createUser([
'administer site configuration',
'administer software updates',
]);
$this->drupalLogin($user);
$this->setProjectInstalledVersion('8.1.0');
$this->checkForUpdates();
$message = t("Warning! Updating this module may cause an error.");
$warning = ValidationResult::createWarning([$message]);
TestSubscriber1::setTestResult([$warning], ReadinessCheckEvent::class);
$this->drupalGet('/admin/automatic-update-extensions');
$this->assertTableShowsUpdates();
$assert->pageTextContains(static::$warningsExplanation);
$assert->pageTextNotContains(static::$errorsExplanation);
$assert->buttonExists('Update');
}
}
......@@ -6,17 +6,26 @@
*/
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\package_manager\Validator\ComposerExecutableValidator;
/**
* Implements hook_help().
*/
function package_manager_help($route_name, RouteMatchInterface $route_match) {
// @todo Fully document all the modules features in
// https://www.drupal.org/i/3253591.
switch ($route_name) {
case 'help.page.package_manager':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Package Manager is an API for installing and updating Drupal core and contributed modules.') . '</p>';
$output .= '<p>' . t('Package Manager is a framework for updating Drupal core and installing contributed modules and themes via Composer. It has no user interface, but it provides an API for creating a temporary copy of the current site, making changes to the copy, and then syncing those changes back into the live site.') . '</p>';
$output .= '<p>' . t('Package Manager dispatches events before and after various operations, and external code can integrate with it by subscribing to those events. For more information, see <code>package_manager.api.php</code>.') . '</p>';
$output .= '<h3>' . t('Requirements') . '</h3>';
$output .= '<p>' . t('Package Manager requires Composer @version or later available as an executable, 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]) . '</p>';
$output .= '<h3>' . t('Limitations') . '</h3>';
$output .= '<p>' . t("Because Package Manager modifies the current site's code base, it is intentionally limited in certain ways to prevent unexpected changes from being made to the live site:") . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('Package Manager can only maintain one copy of the site at any given time. If a copy of the site already exists, another one cannot be created until the existing copy is destroyed.') . '</li>';
$output .= '<li>' . t('The temporary copy of the site is associated with the user or session that originally created it, and only that user or session can make changes to it.') . '</li>';
$output .= '<li>' . t('Modules cannot be uninstalled while Package Manager is syncing changes into live site.') . '<li>';
$output .= '</ul>';
$output .= '<p>' . t('For more information, see the <a href=":package-manager-documentation">online documentation for the Package Manager module</a>.', [':package-manager-documentation' => 'https://www.drupal.org/docs/8/core/modules/package-manager']) . '</p>';
return $output;
}
......
......@@ -41,7 +41,7 @@ class MultisiteValidator implements PreOperationStageValidatorInterface {
public function validateStagePreOperation(PreOperationStageEvent $event): void {
if ($this->isMultisite()) {
$event->addError([
$this->t('Multisites are not supported by Package Manager.'),
$this->t('Drupal multisite is not supported by Package Manager.'),
]);
}
}
......
......@@ -24,7 +24,7 @@ class MultisiteValidatorTest extends PackageManagerKernelTestBase {
TRUE,
[
ValidationResult::createError([
'Multisites are not supported by Package Manager.',
'Drupal multisite is not supported by Package Manager.',
]),
],
],
......
......@@ -42,16 +42,26 @@ class CronUpdater extends Updater {
*/
protected $logger;
/**
* The cron release chooser service.
*
* @var \Drupal\automatic_updates\ReleaseChooser
*/
protected $releaseChooser;
/**
* Constructs a CronUpdater object.
*
* @param \Drupal\automatic_updates\ReleaseChooser $release_chooser
* The cron release chooser service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger channel factory.
* @param mixed ...$arguments
* Additional arguments to pass to the parent constructor.
*/
public function __construct(LoggerChannelFactoryInterface $logger_factory, ...$arguments) {
public function __construct(ReleaseChooser $release_chooser, LoggerChannelFactoryInterface $logger_factory, ...$arguments) {
parent::__construct(...$arguments);
$this->releaseChooser = $release_chooser;
$this->logger = $logger_factory->get('automatic_updates');
}
......@@ -59,50 +69,35 @@ class CronUpdater extends Updater {
* Handles updates during cron.
*/
public function handleCron(): void {
$level = $this->configFactory->get('automatic_updates.settings')
->get('cron');
// If automatic updates are disabled, bail out.
if ($level === static::DISABLED) {
if ($this->isDisabled()) {
return;
}
$recommender = new UpdateRecommender();
try {
$recommended_release = $recommender->getRecommendedRelease(TRUE);
}
catch (\Throwable $e) {
$this->logger->error($e->getMessage());
return;
}
// If we're already up-to-date, there's nothing else we need to do.
if ($recommended_release === NULL) {
return;
$next_release = $this->releaseChooser->refresh()->getLatestInInstalledMinor();
if ($next_release) {
$this->performUpdate($next_release->getVersion());
}
}
$project = $recommender->getProjectInfo();
if (empty($project['existing_version'])) {
/**
* Performs the update.
*
* @param string $update_version
* The version to which to update.
*/
private function performUpdate(string $update_version): void {
$installed_version = (new ProjectInfo())->getInstalledVersion();
if (empty($installed_version)) {
$this->logger->error('Unable to determine the current version of Drupal core.');
return;
}
// If automatic updates are only enabled for security releases, bail out if
// the recommended release is not a security release.
if ($level === static::SECURITY && !$recommended_release->isSecurityRelease()) {
return;
}
// @todo Use the queue to add update jobs allowing jobs to span multiple
// cron runs.
$recommended_version = $recommended_release->getVersion();
// Do the bulk of the update in its own try-catch structure, so that we can
// handle any exceptions or validation errors consistently, and destroy the
// stage regardless of whether the update succeeds.
try {
$this->begin([
'drupal' => $recommended_version,
'drupal' => $update_version,
]);
$this->stage();
$this->apply();
......@@ -110,8 +105,8 @@ class CronUpdater extends Updater {
$this->logger->info(
'Drupal core has been updated from %previous_version to %update_version',
[
'%previous_version' => $project['existing_version'],
'%update_version' => $recommended_version,
'%previous_version' => $installed_version,
'%update_version' => $update_version,
]
);
}
......@@ -138,6 +133,16 @@ class CronUpdater extends Updater {
}
}
/**
* Determines if cron updates are disabled.
*
* @return bool
* TRUE if cron updates are disabled, otherwise FALSE.
*/
private function isDisabled(): bool {
return $this->configFactory->get('automatic_updates.settings')->get('cron') === static::DISABLED;
}
/**
* Generates a log message from a stage validation exception.
*
......
......@@ -2,9 +2,9 @@
namespace Drupal\automatic_updates\Event;
use Drupal\automatic_updates\Updater;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\Stage;
use Drupal\package_manager\ValidationResult;
/**
......@@ -31,14 +31,14 @@ class ReadinessCheckEvent extends PreOperationStageEvent {
/**
* Constructs a ReadinessCheckEvent object.
*
* @param \Drupal\automatic_updates\Updater $updater
* The updater service.
* @param \Drupal\package_manager\Stage $stage
* The stage service.
* @param string[] $project_versions
* (optional) The versions of the packages to update to, keyed by package
* name.
*/
public function __construct(Updater $updater, array $project_versions = []) {
parent::__construct($updater);
public function __construct(Stage $stage, array $project_versions = []) {
parent::__construct($stage);
if ($project_versions) {
if (count($project_versions) !== 1 || !array_key_exists('drupal', $project_versions)) {
throw new \InvalidArgumentException("Currently only updates to Drupal core are supported.");
......
......@@ -4,8 +4,9 @@ namespace Drupal\automatic_updates\Form;
use Drupal\automatic_updates\BatchProcessor;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\ReleaseChooser;
use Drupal\automatic_updates\Updater;
use Drupal\automatic_updates\UpdateRecommender;
use Drupal\automatic_updates\Validation\ReadinessTrait;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Form\FormBase;
......@@ -19,7 +20,6 @@ use Drupal\system\SystemManager;
use Drupal\update\UpdateManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* Defines a form to update Drupal core.
......@@ -53,11 +53,11 @@ class UpdaterForm extends FormBase {
protected $eventDispatcher;
/**
* The current session.
* The release chooser service.
*
* @var \Symfony\Component\HttpFoundation\Session\SessionInterface
* @var \Drupal\automatic_updates\ReleaseChooser
*/
protected $session;
protected $releaseChooser;
/**
* Constructs a new UpdaterForm object.
......@@ -68,14 +68,14 @@ class UpdaterForm extends FormBase {
* The updater service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher service.
* @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
* The current session.
* @param \Drupal\automatic_updates\ReleaseChooser $release_chooser
* The release chooser service.
*/
public function __construct(StateInterface $state, Updater $updater, EventDispatcherInterface $event_dispatcher, SessionInterface $session) {
public function __construct(StateInterface $state, Updater $updater, EventDispatcherInterface $event_dispatcher, ReleaseChooser $release_chooser) {
$this->updater = $updater;
$this->state = $state;
$this->eventDispatcher = $event_dispatcher;
$this->session = $session;
$this->releaseChooser = $release_chooser;
}
/**
......@@ -93,7 +93,7 @@ class UpdaterForm extends FormBase {
$container->get('state'),
$container->get('automatic_updates.updater'),
$container->get('event_dispatcher'),
$container->get('session')
$container->get('automatic_updates.release_chooser')
);
}
......@@ -112,7 +112,7 @@ class UpdaterForm extends FormBase {
// If there's a stage ID stored in the session, try to claim the stage
// with it. If we succeed, then an update is already in progress, and the
// current session started it, so redirect them to the confirmation form.
$stage_id = $this->session->get(BatchProcessor::STAGE_ID_SESSION_KEY);
$stage_id = $this->getRequest()->getSession()->get(BatchProcessor::STAGE_ID_SESSION_KEY);
if ($stage_id) {
try {
$this->updater->claim($stage_id);
......@@ -131,10 +131,24 @@ class UpdaterForm extends FormBase {
'#theme' => 'update_last_check',
'#last' => $this->state->get('update.last_check', 0),
];
$project_info = new ProjectInfo();
$recommender = new UpdateRecommender();
try {
$recommended_release = $recommender->getRecommendedRelease(TRUE);
// @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show
// one release on the form. First, try to show the latest release in the
// currently installed minor. Failing that, try to show the latest
// release in the next minor. If neither of those are available, just
// show the first available release.
$recommended_release = $this->releaseChooser->refresh()->getLatestInInstalledMinor();
if (!$recommended_release) {
$recommended_release = $this->releaseChooser->getLatestInNextMinor();
if (!$recommended_release) {
// @todo Do not list an update that can't be validated in
// https://www.drupal.org/i/3271235.
$updates = $project_info->getInstallableReleases();
$recommended_release = array_pop($updates);
}
}
}
catch (\RuntimeException $e) {
$form['message'] = [
......@@ -148,6 +162,9 @@ class UpdaterForm extends FormBase {
// If we're already up-to-date, there's nothing else we need to do.
if ($recommended_release === NULL) {
// @todo Link to the Available Updates report if there are other updates
// that are not supported by this module in
// https://www.drupal.org/i/3271235.
$this->messenger()->addMessage('No update available');
return $form;
}
......@@ -159,7 +176,7 @@ class UpdaterForm extends FormBase {
],
];
$project = $recommender->getProjectInfo();
$project = $project_info->getProjectInfo();
if (empty($project['title']) || empty($project['link'])) {
throw new \UnexpectedValueException('Expected project data to have a title and link.');
}
......@@ -187,7 +204,7 @@ class UpdaterForm extends FormBase {
'title' => [
'data' => $title,
],
'installed_version' => $project['existing_version'],
'installed_version' => $project_info->getInstalledVersion(),
'recommended_version' => [
'data' => [
// @todo Is an inline template the right tool here? Is there an Update
......
......@@ -2,13 +2,18 @@
namespace Drupal\automatic_updates;
use Composer\Semver\Comparator;
use Composer\Semver\Semver;
use Drupal\automatic_updates_9_3_shim\ProjectRelease;
use Drupal\update\UpdateManagerInterface;
/**
* Determines the recommended release of Drupal core to update to.
* Defines a class for retrieving project information from Update module.
*
* @todo Allow passing a project name to handle more than Drupal core in
* https://www.drupal.org/i/3271240.
*/
class UpdateRecommender {
class ProjectInfo {
/**
* Returns up-to-date project information for Drupal core.
......@@ -26,44 +31,76 @@ class UpdateRecommender {
*/
public function getProjectInfo(bool $refresh = FALSE): array {
$available_updates = update_get_available($refresh);
if (empty($available_updates)) {
throw new \RuntimeException('There was a problem getting update information. Try again later.');
}
$project_data = update_calculate_project_data($available_updates);
return $project_data['drupal'];
}
/**
* Returns the recommended release of Drupal core.
* Gets all releases of Drupal core to which the site can update.
*
* @param bool $refresh
* (optional) Whether to fetch the latest information about available
* updates from drupal.org. This can be an expensive operation, so defaults
* to FALSE.
*
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease|null
* A value object with information about the recommended release, or NULL
* if Drupal core is already up-to-date.
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[]
* An array of possible update releases with release versions as keys. The
* releases are in descending order by version number (i.e., higher versions
* are listed first).
*
* @throws \RuntimeException
* Thrown if $refresh is TRUE and there are no available releases.
*
* @throws \LogicException
* If Drupal core is out of date and the recommended version of cannot be
* determined.
* @todo Remove or simplify this function in https://www.drupal.org/i/3252190.
*/
public function getRecommendedRelease(bool $refresh = FALSE): ?ProjectRelease {
public function getInstallableReleases(bool $refresh = FALSE): array {
$project = $this->getProjectInfo($refresh);
$installed_version = $this->getInstalledVersion();
// If we refreshed and we were able to get available releases we should
// always have at least have the current release stored.
if ($refresh && empty($project['releases'])) {
throw new \RuntimeException('There was a problem getting update information. Try again later.');
}
// If we're already up-to-date, there's nothing else we need to do.
if ($project['status'] === UpdateManagerInterface::CURRENT) {
return NULL;
return [];
}
// If we don't know what to recommend they update to, time to freak out.
elseif (empty($project['recommended'])) {
// If we don't know what to recommend they update to, time to freak out.
throw new \LogicException('Drupal core is out of date, but the recommended version could not be determined.');
}
$installable_releases = [];
if (Comparator::greaterThan($project['recommended'], $installed_version)) {
$release = ProjectRelease::createFromArray($project['releases'][$project['recommended']]);
$installable_releases[$release->getVersion()] = $release;
}
if (!empty($project['security updates'])) {
foreach ($project['security updates'] as $security_update) {
$release = ProjectRelease::createFromArray($security_update);
$version = $release->getVersion();
if (Comparator::greaterThan($version, $installed_version)) {
$installable_releases[$version] = $release;
}
}
}
$sorted_versions = Semver::rsort(array_keys($installable_releases));
return array_replace(array_flip($sorted_versions), $installable_releases);
}
$recommended_version = $project['recommended'];
return ProjectRelease::createFromArray($project['releases'][$recommended_version]);
/**
* Returns the installed project version, according to the Update module.
*
* @param bool $refresh
* (optional) Whether to fetch the latest information about available
* updates from drupal.org. This can be an expensive operation, so defaults
* to FALSE.
*
* @return string
* The installed project version as known to the Update module.
*/
public function getInstalledVersion(bool $refresh = FALSE): string {
$project_data = $this->getProjectInfo($refresh);
return $project_data['existing_version'];
}
}
<?php
namespace Drupal\automatic_updates;
use Composer\Semver\Semver;
use Drupal\automatic_updates\Validator\UpdateVersionValidator;
use Drupal\automatic_updates_9_3_shim\ProjectRelease;
use Drupal\Core\Extension\ExtensionVersion;
/**
* Defines a class to choose a release of Drupal core to update to.
*/
class ReleaseChooser {
use VersionParsingTrait;
/**
* The version validator service.
*
* @var \Drupal\automatic_updates\Validator\UpdateVersionValidator
*/
protected $versionValidator;
/**
* The project information fetcher.
*
* @var \Drupal\automatic_updates\ProjectInfo
*/
protected $projectInfo;
/**
* Constructs an ReleaseChooser object.
*
* @param \Drupal\automatic_updates\Validator\UpdateVersionValidator $version_validator
* The version validator.
*/
public function __construct(UpdateVersionValidator $version_validator) {
$this->versionValidator = $version_validator;
$this->projectInfo = new ProjectInfo();
}
/**
* Refreshes the project information through the Update module.
*
* @return $this
* The called object.
*/
public function refresh(): self {
$this->projectInfo->getProjectInfo(TRUE);
return $this;
}
/**
* Returns the releases that are installable.
*
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[]
* The releases that are installable according to the version validator
* service.
*/
protected function getInstallableReleases(): array {
return array_filter(
$this->projectInfo->getInstallableReleases(),
[$this->versionValidator, 'isValidVersion'],
ARRAY_FILTER_USE_KEY
);
}
/**
* Gets the most recent release in the same minor as a specified version.
*
* @param string $version
* The full semantic version number, which must include a patch version.
*
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease|null
* The most recent release in the minor if available, otherwise NULL.
*
* @throws \InvalidArgumentException
* If the given semantic version number does not contain a patch version.
*/
protected function getMostRecentReleaseInMinor(string $version): ?ProjectRelease {
if (static::getPatchVersion($version) === NULL) {
throw new \InvalidArgumentException("The version number $version does not contain a patch version");
}
$releases = $this->getInstallableReleases();
foreach ($releases as $release) {
if (Semver::satisfies($release->getVersion(), "~$version")) {
return $release;
}
}
return NULL;
}
/**
* Gets the installed version of Drupal core.
*
* @return string
* The installed version of Drupal core.
*/
protected function getInstalledVersion(): string {
return $this->projectInfo->getInstalledVersion();
}
/**
* Gets the latest release in the currently installed minor.
*
* This will only return a release if it passes the ::isValidVersion() method
* of the version validator service injected into this class.
*
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease|null
* The latest release in the currently installed minor, if any, otherwise
* NULL.
*/
public function getLatestInInstalledMinor(): ?ProjectRelease {
return $this->getMostRecentReleaseInMinor($this->getInstalledVersion());
}
/**
* Gets the latest release in the next minor.
*
* This will only return a release if it passes the ::isValidVersion() method
* of the version validator service injected into this class.
*
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease|null
* The latest release in the next minor, if any, otherwise NULL.
*/
public function getLatestInNextMinor(): ?ProjectRelease {
$installed_version = ExtensionVersion::createFromVersionString($this->getInstalledVersion());
$next_minor = $installed_version->getMajorVersion() . '.' . (((int) $installed_version->getMinorVersion()) + 1) . '.0';
return $this->getMostRecentReleaseInMinor($next_minor);
}
}
<?php
namespace Drupal\automatic_updates\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Modifies route definitions.
*
* @internal
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$disabled_routes = [
'update.theme_update',
'system.theme_install',
'update.module_update',
'update.module_install',
'update.status',
'update.report_update',
'update.report_install',
'update.settings',
'system.status',
'update.confirmation_page',
];
foreach ($disabled_routes as $route) {
$route = $collection->get($route);
if ($route) {
$route->setOption('_automatic_updates_readiness_messages', 'skip');
}
}
}
}
......@@ -147,23 +147,8 @@ final class AdminReadinessMessages implements ContainerInjectionInterface {
}
if ($this->adminContext->isAdminRoute() && $this->currentUser->hasPermission('administer site configuration')) {
// These routes don't need additional nagging.
$disabled_routes = [
'update.theme_update',
'system.theme_install',
'update.module_update',
'update.module_install',
'update.status',
'update.report_update',
'update.report_install',
'update.settings',
'system.status',
'update.confirmation_page',
'automatic_updates.report_update',
'automatic_updates.module_update',
'automatic_updates.theme_update',
];
return !in_array($this->currentRouteMatch->getRouteName(), $disabled_routes, TRUE);
$route = $this->currentRouteMatch->getRouteObject();
return $route && $route->getOption('_automatic_updates_readiness_messages') !== 'skip';
}
return FALSE;
}
......
......@@ -4,8 +4,8 @@ namespace Drupal\automatic_updates\Validation;
use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\Updater;
use Drupal\automatic_updates\UpdateRecommender;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
......@@ -102,20 +102,18 @@ class ReadinessValidationManager implements EventSubscriberInterface {
* @return $this
*/
public function run(): self {
$recommender = new UpdateRecommender();
$release = $recommender->getRecommendedRelease(TRUE);
// If updates will run during cron, use the cron updater service provided by
// this module. This will allow subscribers to ReadinessCheckEvent to run
// specific validation for conditions that only affect cron updates.
if ($this->config->get('automatic_updates.settings')->get('cron') == CronUpdater::DISABLED) {
if ($this->config->get('automatic_updates.settings')->get('cron') === CronUpdater::DISABLED) {
$stage = $this->updater;
}
else {
$stage = $this->cronUpdater;
}
$project_versions = $release ? ['drupal' => $release->getVersion()] : [];
$event = new ReadinessCheckEvent($stage, $project_versions);
$event = new ReadinessCheckEvent($stage);
// Version validators will need up-to-date project info.
(new ProjectInfo())->getProjectInfo(TRUE);
$this->eventDispatcher->dispatch($event);
$results = $event->getResults();
$this->keyValueExpirable->setWithExpire(
......
<?php
namespace Drupal\automatic_updates\Validator;
use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\VersionParsingTrait;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\package_manager\Stage;
use Drupal\package_manager\ValidationResult;
/**
* Validates the target version of Drupal core before a cron update.
*
* @internal
* This class is an internal part of the module's cron update handling and
* should not be used by external code.
*/
final class CronUpdateVersionValidator extends UpdateVersionValidator {
use VersionParsingTrait;
/**
* {@inheritdoc}
*/
protected static function isStageSupported(Stage $stage): bool {
return $stage instanceof CronUpdater;
}
/**
* {@inheritdoc}
*/
public function getValidationResult(string $to_version_string): ?ValidationResult {
if ($result = parent::getValidationResult($to_version_string)) {
return $result;
}
$from_version_string = $this->getCoreVersion();
$to_version = ExtensionVersion::createFromVersionString($to_version_string);
$from_version = ExtensionVersion::createFromVersionString($from_version_string);
$variables = [
'@to_version' => $to_version_string,
'@from_version' => $from_version_string,
];
// @todo Return multiple validation messages and summary in
// https://www.drupal.org/project/automatic_updates/issues/3272068.
// Validate that both the from and to versions are stable releases.
if ($from_version->getVersionExtra()) {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, because Automatic Updates only supports updating from stable versions during cron.', $variables),
]);
}
if ($to_version->getVersionExtra()) {
// Because we do not support updating to a new minor version during
// cron it is probably impossible to update from a stable version to
// a unstable/pre-release version, but we should check this condition
// just in case.
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated during cron to the recommended version, @to_version, because Automatic Updates only supports updating to stable versions during cron.', $variables),
]);
}
if ($from_version->getMinorVersion() !== $to_version->getMinorVersion()) {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from one minor version to another are not supported during cron.', $variables),
]);
}
// Only updating to the next patch release is supported during cron.
$supported_patch_version = $from_version->getMajorVersion() . '.' . $from_version->getMinorVersion() . '.' . (((int) static::getPatchVersion($from_version_string)) + 1);
if ($to_version_string !== $supported_patch_version) {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because Automatic Updates only supports 1 patch version update during cron.', $variables),
]);
}
// If both the from and to version numbers are valid check if the current
// settings only allow security updates during cron and if so ensure the
// update release is a security release.
$level = $this->configFactory->get('automatic_updates.settings')->get('cron');
if ($level === CronUpdater::SECURITY) {
$releases = (new ProjectInfo())->getInstallableReleases();
// @todo Remove this check and add validation to
// \Drupal\automatic_updates\Validator\UpdateVersionValidator::getValidationResult()
// to ensure the update release is always secure and supported in
// https://www.drupal.org/i/3271468.
if (!isset($releases[$to_version_string])) {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because @to_version is not a valid release.', $variables),
]);
}
if (!$releases[$to_version_string]->isSecurityRelease()) {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because @to_version is not a security release.', $variables),
]);
}
}
return NULL;
}
}
......@@ -2,20 +2,28 @@
namespace Drupal\automatic_updates\Validator;
use Composer\Semver\Semver;
use Composer\Semver\Comparator;
use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\Updater;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\Event\StageEvent;
use Drupal\package_manager\Stage;
use Drupal\package_manager\ValidationResult;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates that core updates are within a supported version range.
*
* @internal
* This class is an internal part of the module's update handling and
* should not be used by external code.
*/
class UpdateVersionValidator implements EventSubscriberInterface {
......@@ -48,12 +56,7 @@ class UpdateVersionValidator implements EventSubscriberInterface {
* The running core version as known to the Update module.
*/
protected function getCoreVersion(): string {
// We need to call these functions separately, because
// update_get_available() will include the file that contains
// update_calculate_project_data().
$available_updates = update_get_available();
$available_updates = update_calculate_project_data($available_updates);
return $available_updates['drupal']['existing_version'];
return (new ProjectInfo())->getInstalledVersion();
}
/**
......@@ -63,122 +66,141 @@ class UpdateVersionValidator implements EventSubscriberInterface {
* The event object.
*/
public function checkUpdateVersion(PreOperationStageEvent $event): void {
$stage = $event->getStage();
// We only want to do this check if the stage belongs to Automatic Updates.
if (!$stage instanceof Updater) {
if (!static::isStageSupported($event->getStage())) {
return;
}
if ($to_version = $this->getUpdateVersion($event)) {
if ($result = $this->getValidationResult($to_version)) {
$event->addError($result->getMessages(), $result->getSummary());
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
PreCreateEvent::class => 'checkUpdateVersion',
ReadinessCheckEvent::class => 'checkUpdateVersion',
];
}
/**
* Gets the update version.
*
* @param \Drupal\package_manager\Event\StageEvent $event
* The event.
*
* @return string|null
* The version that the site will update to if any, otherwise NULL.
*/
protected function getUpdateVersion(StageEvent $event): ?string {
/** @var \Drupal\automatic_updates\Updater $updater */
$updater = $event->getStage();
if ($event instanceof ReadinessCheckEvent) {
$package_versions = $event->getPackageVersions();
// During readiness checks, we might not know the desired package
// versions, which means there's nothing to validate.
if (empty($package_versions)) {
return;
}
}
else {
// If the stage has begun its life cycle, we expect it knows the desired
// package versions.
$package_versions = $stage->getPackageVersions()['production'];
$package_versions = $updater->getPackageVersions()['production'];
}
if ($package_versions) {
// All the core packages will be updated to the same version, so it
// doesn't matter which specific package we're looking at.
$core_package_name = key($updater->getActiveComposer()->getCorePackages());
return $package_versions[$core_package_name];
}
else {
// During readiness checks we might not have a version to update to. Check
// if there are any possible updates and add a message about why we cannot
// update to that version.
// @todo Remove this code in https://www.drupal.org/i/3272326 when we add
// add a validator that will warn if cron updates will no longer work
// because the site is more than 1 patch release behind.
$project_info = new ProjectInfo();
if ($possible_releases = $project_info->getInstallableReleases()) {
$possible_release = array_pop($possible_releases);
return $possible_release->getVersion();
}
}
return NULL;
}
/**
* Determines if a version is valid.
*
* @param string $version
* The version string.
*
* @return bool
* TRUE if the version is valid (i.e., the site can update to it), otherwise
* FALSE.
*/
public function isValidVersion(string $version): bool {
return empty($this->getValidationResult($version));
}
/**
* Validates if an update to a specific version is allowed.
*
* @param string $to_version_string
* The version to update to.
*
* @return \Drupal\package_manager\ValidationResult|null
* NULL if the update is allowed, otherwise returns a validation result with
* the reason why the update is not allowed.
*/
protected function getValidationResult(string $to_version_string): ?ValidationResult {
$from_version_string = $this->getCoreVersion();
$from_version = ExtensionVersion::createFromVersionString($from_version_string);
// All the core packages will be updated to the same version, so it doesn't
// matter which specific package we're looking at.
$core_package_name = key($stage->getActiveComposer()->getCorePackages());
$to_version_string = $package_versions[$core_package_name];
$to_version = ExtensionVersion::createFromVersionString($to_version_string);
$variables = [
'@to_version' => $to_version_string,
'@from_version' => $from_version_string,
];
$from_version_extra = $from_version->getVersionExtra();
$to_version_extra = $to_version->getVersionExtra();
if (Semver::satisfies($to_version_string, "< $from_version_string")) {
$event->addError([
$this->t('Update version @to_version is lower than @from_version, downgrading is not supported.', $variables),
$from_version = ExtensionVersion::createFromVersionString($from_version_string);
// @todo Return multiple validation messages and summary in
// https://www.drupal.org/project/automatic_updates/issues/3272068.
if ($from_version->getVersionExtra() === 'dev') {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from a dev version to any other version are not supported.', $variables),
]);
}
elseif ($from_version_extra === 'dev') {
$event->addError([
$this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from a dev version to any other version are not supported.', $variables),
if (Comparator::lessThan($to_version_string, $from_version_string)) {
return ValidationResult::createError([
$this->t('Update version @to_version is lower than @from_version, downgrading is not supported.', $variables),
]);
}
elseif ($from_version->getMajorVersion() !== $to_version->getMajorVersion()) {
$event->addError([
$to_version = ExtensionVersion::createFromVersionString($to_version_string);
if ($from_version->getMajorVersion() !== $to_version->getMajorVersion()) {
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from one major version to another are not supported.', $variables),
]);
}
elseif ($from_version->getMinorVersion() !== $to_version->getMinorVersion()) {
if ($from_version->getMinorVersion() !== $to_version->getMinorVersion()) {
if (!$this->configFactory->get('automatic_updates.settings')->get('allow_core_minor_updates')) {
$event->addError([
return ValidationResult::createError([
$this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from one minor version to another are not supported.', $variables),
]);
}
elseif ($stage instanceof CronUpdater) {
$event->addError([
$this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from one minor version to another are not supported during cron.', $variables),
]);
}
}
elseif ($stage instanceof CronUpdater) {
if ($from_version_extra || $to_version_extra) {
if ($from_version_extra) {
$messages[] = $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, because Automatic Updates only supports updating from stable versions during cron.', $variables);
$event->addError($messages);
}
if ($to_version_extra) {
// Because we do not support updating to a new minor version during
// cron it is probably impossible to update from a stable version to
// a unstable/pre-release version, but we should check this condition
// just in case.
$messages[] = $this->t('Drupal cannot be automatically updated during cron to the recommended version, @to_version, because Automatic Updates only supports updating to stable versions during cron.', $variables);
$event->addError($messages);
}
}
else {
$to_patch_version = (int) $this->getPatchVersion($to_version_string);
$from_patch_version = (int) $this->getPatchVersion($from_version_string);
if ($from_patch_version + 1 !== $to_patch_version) {
$messages[] = $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because Automatic Updates only supports 1 patch version update during cron.', $variables);
$event->addError($messages);
}
}
}
return NULL;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
PreCreateEvent::class => 'checkUpdateVersion',
ReadinessCheckEvent::class => 'checkUpdateVersion',
];
}
/**
* Gets the patch number for a version string.
* Determines if a stage is supported by this validator.
*
* @todo Move this method to \Drupal\Core\Extension\ExtensionVersion in
* https://www.drupal.org/i/3261744.
* @param \Drupal\package_manager\Stage $stage
* The stage to check.
*
* @param string $version_string
* The version string.
*
* @return string
* The patch number.
* @return bool
* TRUE if the stage is supported by this validator, otherwise FALSE.
*/
private function getPatchVersion(string $version_string): string {
$version_extra = ExtensionVersion::createFromVersionString($version_string)
->getVersionExtra();
if ($version_extra) {
$version_string = str_replace("-$version_extra", '', $version_string);
}
$version_parts = explode('.', $version_string);
return $version_parts[2];
protected static function isStageSupported(Stage $stage): bool {
// We only want to do this check if the stage belongs to Automatic Updates,
// and it is not a cron update.
// @see \Drupal\automatic_updates\Validator\CronUpdateVersionValidator
return $stage instanceof Updater && !$stage instanceof CronUpdater;
}
}
<?php
namespace Drupal\automatic_updates;
use Drupal\Core\Extension\ExtensionVersion;
/**
* Common function for parsing version traits.
*
* @internal
* This trait may be removed in patch or minor versions.
*/
trait VersionParsingTrait {
/**
* Gets the patch number from a version string.
*
* @todo Move this method to \Drupal\Core\Extension\ExtensionVersion in
* https://www.drupal.org/i/3261744.
*
* @param string $version_string
* The version string.
*
* @return string|null
* The patch number if available, otherwise NULL.
*/
protected static function getPatchVersion(string $version_string): ?string {
$version_extra = ExtensionVersion::createFromVersionString($version_string)
->getVersionExtra();
if ($version_extra) {
$version_string = str_replace("-$version_extra", '', $version_string);
}
$version_parts = explode('.', $version_string);
return count($version_parts) === 3 ? $version_parts[2] : NULL;
}
}