Commit c0773fdb authored by Georges Racinet's avatar Georges Racinet 🦑
Browse files

Mercurial rebase for topics

Closes #280

This is enough for merge requests based on topics, which
currently would display a 'rebase' button if the method is
'ff'.

For now we add a hard check that the branch being rebased is
related to a topic, that makes for a simple revset.

As in the case of squashs, the protection against concurrent
rebases seems at first sight to be unneeded (the rebase itself
is atomic enough, and a subsequent would fail because it'd create
instabilities) but because of #277 we have to be cautious
with concurrency and avoid potential thundering herds of shares.
This will be done for both squash and rebase in a follow-up that
would simply test for presence of a share.

--HG--
branch : heptapod
parent 5a9a4ada6704
......@@ -546,6 +546,104 @@ def squash_in_progress?(squash_id)
false
end
# Perform a rebase, only for topics.
#
# We may be subject to the same race condition than explained in
# https://gitlab.com/gitlab-org/gitlab/-/issues/5966#note_150894306
# in short, there's a chance that the PostReceive gets executed
# before the resulting commit is written in the database.
# That's the kind of thing we will be able to fix once these
# methods are reimplemented in HGitaly (should be after Heptapod 1.0)
def rebase(user, rebase_id, branch:, branch_sha:,
remote_repository:, remote_branch:)
logprefix = "hg_rebase for #{@relative_path} of git #{branch_sha} "\
"(branch #{branch}) onto #{remote_branch}"
logger = Rails.logger
raise StandardError, "Rebasing between two different repos is not "\
"supported" unless remote_repository == self
topic = parse_gitlab_branch_for_hg(branch)[1]
raise HgError, "Only topics can be rebased" if topic.nil?
hg_exe = Gitlab.config.mercurial.bin_path
hg_env = hg_env_for_write(user)
# we need to notify GitLab: the merge request update is done at
# the receiving end of the post-receive hook
hg_env.delete('HEPTAPOD_SKIP_ALL_GITLAB_HOOKS')
# don't see an option to rebase with an obsmarker note
username = hg_prepare_user_encoding(hg_env, user, "")
end_hgsha = hgsha_from_sha(branch_sha)
raise HgError, "Could not find hg changeset for Git #{branch_sha}" if end_hgsha.nil?
# For consistency in corner cases (multiple heads) let's query the
# Git sha for the target branch, even though we could also parse
# and use Mercurial
dest_sha = find_branch(remote_branch)&.target
dest_hgsha = hgsha_from_sha(dest_sha)
raise HgError, "Could not find hg changeset for Git #{branch_sha}" if dest_hgsha.nil?
# revset insisting on using branch_sha because
# - the topic could actually span several named branches (we could also
# use the branch knowledge)
# - if branch_sha is not the branch/topic head, that means something
# is wrong. Instead of rebasing the whole, let's have the error
# we get by refusing instabilities
# topic naming rules make sure that we need no escaping
revset = "topic(#{topic}) and ::#{end_hgsha}"
rebase_hgsha = hg_tmp_workdir(nil) do |share_path|
# TODO would be nice to experiment with in-memory rebase (wouldn't
# need a working dir) but not sure what the good use cases are.
# For instance, is a small rebase on a big repo much more efficient
# in memory or should that precisely be avoided?
# `allowunstable=no` protects us against all instabilities,
# in particular against orphaning dependent topics.
output, status = popen(
[hg_exe, 'rebase',
'-r', revset,
'-d', dest_hgsha,
'--tool', 'internal:merge3',
# extension activation should also end up in py-heptapod's
# required.hgrc, but let's make it work straight away
'--config', 'extensions.rebase=',
'--config', 'rebase.singletransaction=yes',
'--config', 'experimental.evolution.allowunstable=no',
'--config', 'ui.username=' + username,
],
share_path, hg_env)
if status != 0
raise HgError, "Could not rebase revset `#{revset}` "\
"onto #{dest_hgsha} Error: #{output}"
end
logger.info("#{logprefix} rebase done, finding successor")
output, status = popen(
[hg_exe, 'hpd-unique-successor', '-r', end_hgsha],
share_path, hg_env)
if status != 0
raise HgError, "Could not retrieve topic head after rebase "\
"(successor of #{end_hgsha})"
end
output.strip
end
hg_git_invalidate_maps!
rebase_sha = sha_from_hgsha(rebase_hgsha)
logger.info("#{logprefix} rebase successor changeset #{rebase_hgsha} "\
"(git sha #{rebase_sha})")
yield rebase_sha
rebase_sha
end
# first approx, but it'd be better to have a way to tell
# TODO use rebase_id in tmp workdir name (same for squash)
def rebase_in_progress?(rebase_id)
false
end
# Pull a given revision from URL forcing topic on all new changesets
#
# Return:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment