Class: RecreateBranch

Inherits:
Object
  • Object
show all
Includes:
GitHelpersMixin
Defined in:
lib/git_bpf/commands/recreate-branch.rb

Overview

recreate_branch: Recreate a branch based on the merge commits it’s comprised of.

Constant Summary collapse

@@prefix =
"BRANCH-PER-FEATURE-PREFIX"

Instance Method Summary collapse

Methods included from GitHelpersMixin

#branchExists?, #context, #promptYN, #refExists?, #terminate

Instance Method Details

#execute(opts, argv) ⇒ Object



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
181
182
# File 'lib/git_bpf/commands/recreate-branch.rb', line 43

def execute(opts, argv)
  if argv.length != 1
    run('recreate-branch', '--help')
    terminate
  end

  source = argv.pop

  # If no new branch name provided, replace the source branch.
  opts.branch = source if opts.branch == nil

  if not refExists? opts.base
    terminate "Cannot find reference '#{opts.base}' to use as a base for new branch: #{opts.branch}."
  end

  if opts.discard
    unless opts.remote
      repo = Repository.new(Dir.getwd)
      remote_name = repo.config(true, "--get", "gitbpf.remotename").chomp
      opts.remote = remote_name.empty? ? 'origin' : remote_name
    end
    git('fetch', opts.remote)
    if branchExists?(source, opts.remote)
      opoo "This will delete your local '#{source}' branch if it exists and create it afresh from the #{opts.remote} remote."
      if not promptYN "Continue?"
        terminate "Aborting."
      end
      git('checkout', opts.base)
      git('branch', '-D', source) if branchExists? source
      git('checkout', source)
    end
  end

  # Perform some validation.
  if not branchExists? source
    terminate "Cannot recreate branch #{source} as it doesn't exist."
  end

  if opts.branch != source and branchExists? opts.branch
    terminate "Cannot create branch #{opts.branch} as it already exists."
  end

  #
  # 1. Compile a list of merged branches from source branch.
  #
  ohai "1. Processing branch '#{source}' for merge-commits..."

  branches = getMergedBranches(opts.base, source)

  if branches.empty?
    terminate "No feature branches detected, '#{source}' matches '#{opts.base}'."
  end

  if opts.list
    terminate "Branches to be merged:\n#{branches.shell_list}"
  end

  # Remove from the list any branches that have been explicity excluded using
  # the -x option
  branches.reject! do |item|
    stripped = item.gsub /^remotes\/\w+\/([\w\-\/]+)$/, '\1'
    opts.exclude.include? stripped
  end

  # Prompt to continue.
  opoo "The following branches will be merged when the new #{opts.branch} branch is created:\n#{branches.shell_list}"
  puts
  puts "If you see something unexpected check:"
  puts "a) that your '#{source}' branch is up to date"
  puts "b) if '#{opts.base}' is a branch, make sure it is also up to date."
  opoo "If there are any non-merge commits in '#{source}', they will not be included in '#{opts.branch}'. You have been warned."
  if not promptYN "Proceed with #{source} branch recreation?"
    terminate "Aborting."
  end

  #
  # 2. Backup existing local source branch.
  #
  tmp_source = "#{@@prefix}-#{source}"
  ohai "2. Creating backup of '#{source}', '#{tmp_source}'..."

  if branchExists? tmp_source
    terminate "Cannot create branch #{tmp_source} as one already exists. To continue, #{tmp_source} must be removed."
  end

  git('branch', '-m', source, tmp_source)

  #
  # 3. Create new branch based on 'base'.
  #
  ohai "3. Creating new '#{opts.branch}' branch based on '#{opts.base}'..."

  git('checkout', '-b', opts.branch, opts.base, '--quiet')

  #
  # 4. Begin merging in feature branches.
  #
  ohai "4. Merging in feature branches..."

  branches.each do |branch|
    begin
      puts " - '#{branch}'"
      # Attempt to merge in the branch. If there is no conflict at all, we
      # just move on to the next one.
      git('merge', '--quiet', '--no-ff', '--no-edit', branch)
    rescue
      # There was a conflict. If there's no available rerere for it then it is
      # unresolved and we need to abort as there's nothing that can be done
      # automatically.
      conflicts = git('rerere', 'status').chomp.split("\n")

      if conflicts.length != 0
        puts "\n"
        puts "There is a merge conflict with branch #{branch} that has no rerere."
        puts "Record a resoloution by resolving the conflict."
        puts "Then run the following command to return your repository to its original state."
        puts "\n"
        puts "git checkout #{tmp_source} && git branch -D #{opts.branch} && git branch -m #{opts.branch}"
        puts "\n"
        puts "If you do not want to resolve the conflict, it is safe to just run the above command to restore your repository to the state it was in before executing this command."
        terminate
      else
        # Otherwise, we have a rerere and the changes have been staged, so we
        # just need to commit.
        git('commit', '-a', '--no-edit')
      end
    end
  end

  #
  # 5. Clean up.
  #
  ohai "5. Cleaning up temporary branches ('#{tmp_source}')."

  if source != opts.branch
    git('branch', '-m', tmp_source, source)
  else
    git('branch', '-D', tmp_source)
  end
end

#getMergedBranches(base, source) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/git_bpf/commands/recreate-branch.rb', line 184

def getMergedBranches(base, source)
  branches = []
  merges = git('rev-list', '--parents', '--merges', '--reverse', "#{base}...#{source}").strip

  merges.split("\n").each do |commits|
    parents = commits.split("\s")
    commit = parents.shift

    parents.each do |parent|
      name = git('name-rev', parent, '--name-only').strip
      alt_base = git('name-rev', base, '--name-only').strip
      remote_heads = /remote\/\w+\/HEAD/
      unless name.include? source or name.include? alt_base or name.match remote_heads
        # Make sure not to include the tilde part of a branch name (e.g. '~2')
        # as this signifies a commit that's behind the head of the branch but
        # we want to merge in the head of the branch.
        name = name.partition('~')[0]
        # This can lead to duplicate branches, because the name may have only
        # differed in the tilde portion ('mybranch~1', 'mybranch~2', etc.)
        branches.push name unless branches.include? name
      end
    end
  end

  return branches
end

#options(opts) ⇒ Object



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
# File 'lib/git_bpf/commands/recreate-branch.rb', line 16

def options(opts)
  opts.base = 'master'
  opts.exclude = []

  [
    ['-a', '--base NAME',
      "A reference to the commit from which the source branch is based, defaults to #{opts.base}.",
      lambda { |n| opts.base = n }],
    ['-b', '--branch NAME',
      "Instead of deleting the source branch and replacng it with a new branch of the same name, leave the source branch and create a new branch called NAME.",
      lambda { |n| opts.branch = n }],
    ['-x', '--exclude NAME',
      "Specify a list of branches to be excluded.",
      lambda { |n| opts.exclude.push(n) }],
    ['-l', '--list',
      "Process source branch for merge commits and list them. Will not make any changes to any branches.",
      lambda { |n| opts.list = true }],
    ['-d', '--discard',
      "Discard the existing local source branch and checkout a new source branch from the remote if one exists. If no remote is specified with -r, gitbpf will use the configured remote, or origin if none is configured.",
      lambda { |n| opts.discard = true }],
    ['-r', '--remote NAME',
      "Specify the remote repository to work with. Only works with the -d option.",
      lambda { |n| opts.remote = n }],

  ]
end