Skip to content

Commit

Permalink
Merge pull request #218 from krodyrobi/merge_retry_failed_push
Browse files Browse the repository at this point in the history
Merge retry failed push fixes #201
  • Loading branch information
vtemian committed Feb 10, 2016
2 parents 392f739 + 3117876 commit 078331f
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 15 deletions.
32 changes: 22 additions & 10 deletions gitfs/merges/accept_mine.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ def _create_remote_copy(self, branch_name, upstream, new_branch):
return local

def _create_local_copy(self, branch_name, new_branch):
old_branch = self.repository.lookup_branch(branch_name,
pygit2.GIT_BRANCH_LOCAL)
return old_branch.rename(new_branch, True)
branch = self.repository.lookup_branch(branch_name,
pygit2.GIT_BRANCH_LOCAL)

def __call__(self, local_branch, remote_branch, upstream):
branch_commit = branch.get_object()

return self.repository.create_branch(new_branch, branch_commit)

def merge(self, local_branch, remote_branch, upstream):
log.debug("AcceptMine: Copy local branch to merging_local")
local = self._create_local_copy(local_branch, "merging_local")

Expand Down Expand Up @@ -85,16 +88,25 @@ def __call__(self, local_branch, remote_branch, upstream):
self.repository.create_reference("refs/heads/%s" % local_branch,
ref.target,
force=True)

def clean_up(self, local_branch):
self.repository.checkout("refs/heads/%s" % local_branch,
strategy=pygit2.GIT_CHECKOUT_FORCE)

log.debug("AcceptMine: Delete merging_local")
ref = self.repository.lookup_reference("refs/heads/merging_local")
ref.delete()
refs = [(target, "refs/heads/" + target) for target in ["merging_local", "merging_remote"]]

for branch, ref in refs:
log.debug("AcceptMine: Delete %s" % branch)
self.repository.lookup_reference(ref).delete()

log.debug("AcceptMine: Delete merging_remote")
ref = self.repository.lookup_reference("refs/heads/merging_remote")
ref.delete()
def __call__(self, local_branch, remote_branch, upstream):
try:
self.merge(local_branch, remote_branch, upstream)
except:
log.exception("AcceptMine: Failed to merge")
raise
finally:
self.clean_up(local_branch)

def solve_conflicts(self, conflicts):
if conflicts:
Expand Down
20 changes: 18 additions & 2 deletions gitfs/worker/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import random

import time
from six.moves.queue import Empty

import pygit2
Expand Down Expand Up @@ -86,8 +88,19 @@ def on_idle(self):
log.info("Get some commits")
self.commit(self.commits)
self.commits = []

count = 0
log.debug("Start syncing")
self.sync()
while not self.sync():
if count < 5:
count += 1

fuzz = random.randint(0, 1000) / 1000
wait = 2 ** count + fuzz

log.debug("Failed. Going to sleep for %d seconds", wait)
time.sleep(wait)
log.debug("Retry-ing")

def merge(self):
log.debug("Start merging")
Expand All @@ -113,7 +126,7 @@ def sync(self):
need_to_push = True
except:
log.exception("Merge failed")
return
return False

if need_to_push:
try:
Expand All @@ -132,10 +145,13 @@ def sync(self):
push_successful.clear()
fetch.set()
log.exception("Push failed")
return False
else:
sync_done.set()
syncing.clear()

return True

def commit(self, jobs):
if len(jobs) == 1:
message = jobs[0]['params']['message']
Expand Down
7 changes: 5 additions & 2 deletions tests/mergers/test_accept_mine.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ def test_create_local_copy(self):
mocked_repo = MagicMock()
mocked_branch = MagicMock()

mocked_branch.rename.return_value = "branch"
mocked_branch.get_object.return_value = "local_commit"
mocked_repo.lookup_branch.return_value = mocked_branch
mocked_repo.create_branch.return_value = "branch"

mine = AcceptMine(mocked_repo)
assert mine._create_local_copy("old_branch", "new_branch") == "branch"

mocked_repo.lookup_branch.assert_called_once_with("old_branch",
GIT_BRANCH_LOCAL)
mocked_branch.rename.assert_called_once_with("new_branch", True)
assert mocked_branch.get_object.call_count == 1
mocked_repo.create_branch.assert_called_once_with("new_branch",
"local_commit")

def test_create_remote_copy(self):
mocked_repo = MagicMock()
Expand Down
42 changes: 41 additions & 1 deletion tests/workers/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from mock import MagicMock, patch
from _pygit2 import GitError

from mock import MagicMock, patch, call
from six.moves.queue import Empty
import pygit2
import pytest
Expand Down Expand Up @@ -111,6 +112,45 @@ def test_sync(self):
assert mocked_repo.behind is False
mocked_repo.push.assert_called_once_with(upstream, branch)

def test_sync_with_push_conflict(self):
upstream = "origin"
branch = "master"
mocked_repo = MagicMock()
mocked_merge = MagicMock()
mocked_sync_done = MagicMock()
mocked_syncing = MagicMock()
mocked_push_successful = MagicMock()
mocked_fetch = MagicMock()
mocked_strategy = MagicMock()

mocked_repo.behind = True
mocked_repo.ahead = MagicMock(1)
mocked_repo.push.side_effect = [GitError("Mocked error"), None]

with patch.multiple('gitfs.worker.sync', sync_done=mocked_sync_done,
syncing=mocked_syncing,
push_successful=mocked_push_successful,
fetch=mocked_fetch):
worker = SyncWorker("name", "email", "name", "email",
repository=mocked_repo,
strategy=mocked_strategy,
upstream=upstream, branch=branch)
worker.merge = mocked_merge

while not worker.sync():
pass

assert mocked_syncing.clear.call_count == 1
assert mocked_push_successful.clear.call_count == 1
assert mocked_sync_done.clear.call_count == 2
assert mocked_sync_done.set.call_count == 1
assert mocked_fetch.set.call_count == 1
assert mocked_push_successful.set.call_count == 1
assert mocked_repo.behind is False
assert mocked_repo.ahead.call_count == 2

mocked_repo.push.assert_has_calls([call(upstream, branch), call(upstream, branch)])

def test_commit_with_just_one_job(self):
mocked_repo = MagicMock()

Expand Down

0 comments on commit 078331f

Please sign in to comment.