1#

2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>

3#

4# This program is free software: you can redistribute it and/or modify

5# it under the terms of the GNU Lesser General Public License as published by

6# the Free Software Foundation, either version 3 of the License, or

7# (at your option) any later version.

8#

9# This program is distributed in the hope that it will be useful,

10# but WITHOUT ANY WARRANTY; without even the implied warranty of

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

12# GNU Lesser General Public License for more details.

13#

14# You should have received a copy of the GNU Lesser General Public License

15# along with this program. If not, see <http://www.gnu.org/licenses/>.

16

17 import logging

18 from copy import deepcopy

19

20 from gitlabracadabra.objects.object import GitLabracadabraObject

21

22 logger = logging.getLogger(__name__)

23

24

25 class MilestonesMixin(GitLabracadabraObject):

26 """Object with milestones."""

27

28 """_process_milestones()

29

30 Process the milestones param.

31 """

32

33 def _process_milestones(self, param_name, param_value, *, dry_run=False, skip_save=False):

34 assert param_name == "milestones" # noqa: S101

35 assert not skip_save # noqa: S101

36 unknown_milestones = self._content.get("unknown_milestones", "warn")

37 try:

38 current_milestones = dict(

39 [

40 [current_milestone.title, current_milestone]

41 for current_milestone in self._obj.milestones.list(all=True)

42 ]

43 )

44 except AttributeError:

45 # https://github.com/python-gitlab/python-gitlab/pull/847

46 logger.error(

47 "[%s] Unable to manage milestones: %s", self._name, "group milestones requires python-gitlab >= 1.1.0"

48 )

49 return

50 target_milestones = dict(

51 [[target_milestone["title"], deepcopy(target_milestone)] for target_milestone in param_value]

52 )

53 # We first check for already existing milestones

54 for current_milestone_title, current_milestone in sorted(current_milestones.items()):

55 if current_milestone_title in target_milestones:

56 for target_milestone_param_name, target_milestone_param_value in sorted(

57 target_milestones[current_milestone_title].items()

58 ):

59 try:

60 current_milestone_param_value = getattr(current_milestone, target_milestone_param_name)

61 except AttributeError:

62 logger.info(

63 "[%s] NOT Changing milestone %s %s: %s -> %s (current value is not available)",

64 self._name,

65 current_milestone_title,

66 target_milestone_param_name,

67 None,

68 target_milestone_param_value,

69 )

70 continue

71 if current_milestone_param_value is None: 71 ↛ 72 line 71 didn't jump to line 72 because the condition on line 71 was never true

72 current_milestone_param_value = ""

73 if current_milestone_param_value != target_milestone_param_value:

74 if target_milestone_param_name == "state":

75 target_milestone_param_name = "state_event"

76 if target_milestone_param_value == "closed": 76 ↛ 79 line 76 didn't jump to line 79 because the condition on line 76 was always true

77 target_milestone_param_value = "close"

78 else:

79 target_milestone_param_value = "activate"

80 if dry_run: 80 ↛ 81 line 80 didn't jump to line 81 because the condition on line 80 was never true

81 logger.info(

82 "[%s] NOT Changing milestone %s %s: %s -> %s (dry-run)",

83 self._name,

84 current_milestone_title,

85 target_milestone_param_name,

86 current_milestone_param_value,

87 target_milestone_param_value,

88 )

89 else:

90 logger.info(

91 "[%s] Changing milestone %s %s: %s -> %s",

92 self._name,

93 current_milestone_title,

94 target_milestone_param_name,

95 current_milestone_param_value,

96 target_milestone_param_value,

97 )

98 setattr(current_milestone, target_milestone_param_name, target_milestone_param_value)

99 current_milestone.save()

100 target_milestones.pop(current_milestone_title)

101 elif unknown_milestones in ["delete", "remove"]: 101 ↛ 107 line 101 didn't jump to line 107 because the condition on line 101 was always true

102 if dry_run: 102 ↛ 103 line 102 didn't jump to line 103 because the condition on line 102 was never true

103 logger.info("[%s] NOT Removing milestone %s (dry-run)", self._name, current_milestone_title)

104 else:

105 logger.info("[%s] Removing milestone %s", self._name, current_milestone_title)

106 current_milestone.delete()

107 elif unknown_milestones not in ["ignore", "skip"]:

108 logger.warning("[%s] NOT Removing milestone: %s", self._name, current_milestone_title)

109 # Remaining milestones

110 for target_milestone_title, target_milestone in sorted(target_milestones.items()):

111 if dry_run: 111 ↛ 112 line 111 didn't jump to line 112 because the condition on line 111 was never true

112 logger.info(

113 "[%s] NOT Adding milestone %s: %s -> %s (dry-run)",

114 self._name,

115 target_milestone_title,

116 None,

117 target_milestone,

118 )

119 else:

120 logger.info(

121 "[%s] Adding milestone %s: %s -> %s", self._name, target_milestone_title, None, target_milestone

122 )

123 self._obj.milestones.create(target_milestone)