summaryrefslogtreecommitdiff
path: root/kci/merger.rb
blob: 50767ee440f3b03bede3615a37b0173df27173d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Copyright (C) 2014-2016 Harald Sitter <sitter@kde.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) version 3, or any
# later version accepted by the membership of KDE e.V. (or its
# successor approved by the membership of KDE e.V.), which shall
# act as a proxy defined in Section 6 of version 3 of the license.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.  If not, see <http://www.gnu.org/licenses/>.

require_relative '../ci-tooling/lib/kci'
require_relative '../lib/merger'

# Merger merges delpoyment branches into KCI integration branches and KCI
# integration branches into one another.
class KCIMerger < Merger
  # Creates a new KCIMerger. Creates a logger, sets up dpkg-mergechangelogs and
  # opens Dir.pwd as a Git::Base.
  def initialize
    super
    @git = @repo
    @push_pending = []
  end

  def remote_branch(name)
    @git.branches.remote.select { |b| b.name == name }.fetch(0, nil)
  end

  def merge_archive_in_backports(series)
    source = "kubuntu_#{series}_archive"
    target = "kubuntu_#{series}_backports"
    @log.unknown "#{source} -> #{target}"
    target = @git.branches.remote.select { |b| b.name == target }.fetch(0, nil)
    return @log.error 'There is no backports branch!' unless target
    merge(source, target)
  end

  def merge_backports_or_archive_in_stable_or_unstable(series)
    @log.unknown "archive | backports -> stable | unstable (#{series})"
    source = remote_branch("kubuntu_#{series}_backports")
    source = remote_branch("kubuntu_#{series}_archive") unless source
    target = remote_branch("kubuntu_stable_#{series}")
    target = remote_branch("kubuntu_unstable_#{series}") unless target
    if KCI.latest_series == series
      target = remote_branch('kubuntu_stable')
      target = remote_branch('kubuntu_unstable') unless target
      raise 'There is no stable or unstable branch!' unless target
    end
    return @log.error 'There is no backports or archive branch!' unless source
    return @log.error 'There is no stable or unstable branch!' unless target
    merge(source, target)
  end

  def merge_in_variant(type, series)
    @log.unknown "#{type} -> variant (#{series})"
    source = remote_branch("kubuntu_#{type}_#{series}")
    source = remote_branch("kubuntu_#{type}") if KCI.latest_series == series
    return @log.error "There is no #{type} branch!" unless source
    merge_variants(source)
  end

  def merge_stable_in_unstable(series)
    @log.unknown "stable -> unstable (#{series})"
    source = remote_branch("kubuntu_stable_#{series}")
    target = remote_branch("kubuntu_unstable_#{series}")
    if KCI.latest_series == series
      source = remote_branch('kubuntu_stable')
      target = remote_branch('kubuntu_unstable')
    end
    return @log.error 'There is no stable branch!' unless source
    return @log.error 'There is no unstable branch!' unless target
    merge(source, target)
    merge_variants(target)
  end

  def merge_variants(typebase)
    # FIXME: should make sure typebase exists?
    typebase = typebase.name if typebase.respond_to?(:name)
    @git.branches.remote.each do |target|
      next unless target.name.start_with?("#{typebase}_")
      @log.info "  #{typebase} -> #{target}"
      merge(typebase, target)
    end
  end

  def run(trigger_branch)
    @log.info "triggered by #{trigger_branch}"

    @push_pending = []

    # NOTE: trigger branches must be explicitly added to the jenkins job class
    #       as such. Otherwise the merger job will not start.

    # Sort series by version, then merge in that order (i.e. oldest first).
    series = KCI.series.dup
    series = series.sort_by { |_, version| Gem::Version.new(version) }.to_h
    series.each_key do |s|
      # archive -> backports
      merge_archive_in_backports(s)
      # s_backports | s_archive -> s_stable | s_unstable | stable | unstable
      merge_backports_or_archive_in_stable_or_unstable(s)
      # s_stable | stable -> _variant
      merge_in_variant('stable', s)
      # stable -> unstable
      merge_stable_in_unstable(s)
      # s_unstable | unstable -> _variant
      merge_in_variant('unstable', s)
    end

    push_all_pending
  end

  private

  # Merges source into target and pushes the merge result.
  # @param source either a Git::Branch or a String specifying the branch from
  #   which should be merged
  # @param target either a Git::Branch or a String specifying the branch in
  #   which  should be merged
  def merge(source, target)
    # We want the full branch name of the remote to work with
    # Try to pick a local version of the remote if available to support
    # postponed pushes.
    # FIXME: as with the clean branches stuff this is a major workaround for
    #        a design flaw in that primary merge targets always want the
    #        remote. For example if we have stable and unstable then we merge
    #        crap into stable and we want remote crap there rather than any
    #        local version of remote.
    #        On the other hand we then merge stable into unstable and there
    #        we very much want the local version rather than the remote one
    #        as otherwise we'd be missing data.
    source_name = source.clone
    source_name = source.name if source.respond_to?(:name)
    source = @git.branches.local.select { |b| b.name == source_name }
    if source.empty?
      source = @git.branches.remote.select { |b| b.name == source_name }
    end
    if source.size != 1
      @log.warn "Apparently there is no branch named #{source_name}!"
      return
    end
    source = source.first
    target = target.name if target.respond_to?(:name)
    @git.checkout(target)
    msg = "Merging #{source.full} into #{target}."
    if noci_merge?(source)
      msg = "Merging #{source.full} into #{target}.\n\nNOCI"
    end
    @log.info msg
    @git.merge(source.full, msg)
    @push_pending << target
  end

  def push_all_pending
    # Coerce Git::Branch entities into strings
    @push_pending.collect! { |x| x.respond_to?(:name) ? x.name : x }
    @push_pending.uniq!
    # Attempting to push nothing fails on detatched heads (see cleanup)
    @log.info @git.push('origin', @push_pending) unless @push_pending.empty?
    @push_pending = []
  end
end

# :nocov:
if __FILE__ == $PROGRAM_NAME
  KCIMerger.new.run(ENV['GIT_BRANCH'])
  sleep(5)
end
# :nocov: