Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support rebasing through GitLab's api #160

Merged
merged 13 commits into from
Mar 1, 2019

Conversation

jcpetruzza
Copy link
Contributor

Here's an attempt to add support for GitLab's new "merge_request/rebase" endpoint (#157), which should enable using marge in public gitlab instances (#159). It is enabled with --rebase-remotely and conflicts with --use-merge-strategy and all the add-xxx options.

It works by performing the rebase locally but replacing the git push --force step by a call to this endpoint.

Because the change happens inside MergeJob.update_from_target_branch_and_push(), it wouldn't have been picked by the mocklab tests, where this method was being replaced by a mock. So I went down a bit of a rabbit hole at first and started by adding a mock of git, to fix this. The mock works by hooking into the Repo.git() method and interpreting the git commands that marge emits, using an extremely simplified model of git.

Because now these tests can see inside update_from_target_branch_and_push(), I make it so that they run on (almost) all combinations of fusion-strategies and trailer-adding, which caught a small bug in the handling of protected branches.

While the PR is a bit large, the individual commits should be smallish and self-contained..

Daniel Gorin added 12 commits January 27, 2019 12:14
The argument parser would end up being called with the
`sys.argv` of `py.test`, so the only way in which this test
could ever pass was if `py.test` was ran without arguments!
Add the counterpart of Mocklab but for the
execution of git commands on the local repository.

This means that we no longer need to mock the
calls to `Job.update_from_target_branch_and_push`
and can actually cover all paths of execution.
In particular, we can now run all tests for
both `merge` and `rebase`.

The mock works by redefining the `Repo.git()` method,
so that it understands the set of git commands that
marge runs. It keeps an extremely simplified model
of a git repository (plus the remote repositories
to which it connects) and uses this to implement
each command

In addition, we use fixtures to try the cross product of
parameters (merge/rebase; add-tested yes/no;
add-part-of yes/no) which actually found minor errors
(fixed in next commit)
In the first version of the code, `branch_rewritten` was meant
to be a flag that would indicate whether the code managed to
go past the `add_trailers()`, so that in the exception handlers
we could know in where the exception came from. But because the
name was ambiguous, in f956e70 it was apparently changed to
reflect whether the a rewrite was performed at all after the
rebase/merge. However, in some places of the error handling logic,
it was still being used as the former in some places.

So we rename the variables that are meant to be flagposts to
have a hopefully more clear meaning, and fix the condition when
we check if the branch is protected: we need to compare the final
commit to the initial commit, to find out if we were actually trying
to push new branch.

This bug was found by the refactoring of the single-job tests.
While this was correct, it meant that in the
`test_waits_for_approvals()` unit test, we would have to wait
put a relatively high number of seconds to ensure we don't
race and fail because we don't see the log message.
Since we are now checking many more combinations of cases,
this started to be noticeable in the speed of the test
They can return a 202 with empty body in a rebase request
It makes the rest call and keeps polling until it gets a result.
At the moment, the only mechanism for synchronization that we have
is `git push --force`. But we will soon add a rebase via the api
as an alternative mechanism (to be used in specific cases)
We replace the boolean `use_merge_stratategy` by an enum
type. We add `gitlab_rebase` as one of the possible enum
values. At the moment it does nothing, but we have prepared
the code/tests to handle this strategy.
On a transition one can now specify a side-effect that will be run
when the endpoint is called. We will need something like that in order
to update the `remote_repo` of the mock of git when a rebase is done
on the mock of gitlab.
We do the rebase locally and instead of pushing, request
a rebase and check that the resulting sha is what we expect.

We ignore all commit tagging options, but this needs to be
validated on the app
@mpickering
Copy link

This won't fix the batch job working with forks. So won't fix the real problem which is present in #157

@jcpetruzza
Copy link
Contributor Author

jcpetruzza commented Feb 7, 2019

@mpickering IIUC, in batch mode, after a batch passes the tests, it is first pushed to the target branch and then all the PRs are rebased, and because the changes are already there, gitlab notices the PR is a no-op and closes it. If the rebasing of the PR is done through the API I expect the same to happen.

I'm probably missing something?

@mpickering
Copy link

Yes sorry, the problem is that after rebasing _repo.fast_forward tries to fetch the wrong branch as local isn't passed to it.

My attempted fix is something like - mpickering@c5b0c59

@jcpetruzza
Copy link
Contributor Author

I see what you mean now! Yes, the batch processing code seems to be assuming that the rebased branch will be already in the repository (which made sense when forks where not handled) and, when using gitlab-rebase, we'd need to fetch the changes beforehand as you did there.

@mpickering do you want to add a commit on top of this PR?

@mpickering
Copy link

Does my change look correct? It felt a bit hacky to me. I'm also unsure how to test this locally, I just deployed my fix and have yet to see if it actually works.

@mpickering
Copy link

@jcpetruzza Can you please tell me how to run the tests so that I can add a test where a MR comes from a fork? It has been a nightmare trying to get this to work with a 6hr CI turnaround.

@mpickering
Copy link

mpickering commented Feb 8, 2019

I tried just running nix-build as CI does but from a fresh checkout it fails nearly immediately for me 🤷‍♂️

distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('pytest-runner')

The same thing happens on this branch.

@jcpetruzza
Copy link
Contributor Author

@mpickering You can do nix-shell --run py.test to run everything. When working on this, I would launch a nix-shell and run py.test tests/test_foo.py, and for a faster feedback loop, you can restrict the tests to run with -k test_name. You may want to edit setup.py and temporarily comment out the line that says:

addopts = --flake8 --pylint --cov=marge

since pylint is super slow (there must be a better way of doing this, but well)

@jcpetruzza
Copy link
Contributor Author

Also, instead of doing the checkout yourself as in your commit, would it work if, whenever the rebase was done by gitlab, you pass the source_repo_url to fast_forward() method (and don't use local=True)? If I'm reading the code correctly, this would first do the git fetch as is needed in this case.

@mpickering
Copy link

@jcpetruzza I will try and add some tests tonight and once I have added those, will feel a lot more confident refactoring.

@mpickering
Copy link

Just to update this. I tried adding some tests but it was too complicated for me and the tests for batch mode are quite lacking to base things from.

However, I eventually got things to work properly but you should be aware that there is a race condition present in the rebase API where the API will say that the rebase has concluded but the change is not reflected yet in the remote. I had to add a big sleep (2 minutes) to be sure that this situation didn't happen.

You can see the changes I made on my branch if you're interested in merging them into upstream. I'm very reluctant to do any refactoring now though due to the lack of tests.

https://github.com/mpickering/marge-bot

@aschmolck aschmolck merged commit 1560707 into smarkets:master Mar 1, 2019
@kgilden
Copy link

kgilden commented Apr 11, 2019

@mpickering @jcpetruzza @aschmolck I'm trying it out and indeed it seems to hit a race condition. It basically tries to rebase twice, which in our case causes the pipeline to be skipped. That in turns makes it possible to immediately merge the branch before waiting until the tests on the branch actually pass. Please see the screenshot below.

2019-04-11-110340_286x322_scrot

Looks like marge-bot also complains about someone skipping the queue, but no-one was merging any other branches at the same time.

2019-04-11-110924_622x378_scrot

Any ways to work around this?

@mpickering
Copy link

We are using a fork of marge now (in order to fix the batch merge mode). The only workaround I know is to increase the timeouts.

@Okhoshi
Copy link

Okhoshi commented Jan 20, 2021

The problem of race condition is due to Gitlab not returning rebase_in_progress on Get Single MR by default. As it is an asynchronous operation, you need to provide include_rebase_in_progress parameter when querying the MR state (as per https://docs.gitlab.com/ee/api/merge_requests.html#rebase-a-merge-request).

@kdsnice
Copy link

kdsnice commented Sep 1, 2022

#348 adding include_rebase_in_progress parameter and prevents marge-bot to complain about someone skipping the queue on each merge request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants