diff --git a/git_cl.py b/git_cl.py index a5143d8cd..f004caf0d 100755 --- a/git_cl.py +++ b/git_cl.py @@ -2204,32 +2204,38 @@ class Changelist(object): change_desc): """Upload the current branch to Gerrit, retry if new remote HEAD is found. options and change_desc may be mutated.""" + remote, remote_branch = self.GetRemoteBranch() + branch = GetTargetRef(remote, remote_branch, options.target_branch) + try: return self._CMDUploadChange(options, git_diff_args, custom_cl_base, - change_desc) + change_desc, branch) except GitPushError as e: - remote, remote_branch = self.GetRemoteBranch() - should_retry = remote_branch == DEFAULT_OLD_BRANCH and \ - gerrit_util.GetProjectHead( - self._gerrit_host, self.GetGerritProject()) == 'refs/heads/main' - if not should_retry: + # Repository might be in the middle of transition to main branch as + # default, and uploads to old default might be blocked. + if remote_branch not in [DEFAULT_OLD_BRANCH, DEFAULT_NEW_BRANCH]: + DieWithError(str(e), change_desc) + + project_head = gerrit_util.GetProjectHead(self._gerrit_host, + self.GetGerritProject()) + if project_head == branch: DieWithError(str(e), change_desc) + branch = project_head - print("WARNING: Detected HEAD change in upstream, fetching remote state") - RunGit(['fetch', remote]) + print("WARNING: Fetching remote state and retrying upload to default " + "branch...") + RunGit(['fetch', '--prune', remote]) options.edit_description = False options.force = True try: - self._CMDUploadChange(options, git_diff_args, custom_cl_base, change_desc) + self._CMDUploadChange(options, git_diff_args, custom_cl_base, + change_desc, branch) except GitPushError as e: DieWithError(str(e), change_desc) def _CMDUploadChange(self, options, git_diff_args, custom_cl_base, - change_desc): + change_desc, branch): """Upload the current branch to Gerrit.""" - remote, remote_branch = self.GetRemoteBranch() - branch = GetTargetRef(remote, remote_branch, options.target_branch) - if options.squash: self._GerritCommitMsgHookCheck(offer_removal=not options.force) if self.GetIssue(): diff --git a/tests/git_cl_test.py b/tests/git_cl_test.py index 480678b44..9d91cc981 100755 --- a/tests/git_cl_test.py +++ b/tests/git_cl_test.py @@ -238,6 +238,7 @@ class TestGitClBasic(unittest.TestCase): cl = git_cl.Changelist() options = optparse.Values() + options.target_branch = 'refs/heads/bar' with self.assertRaises(SystemExitMock): cl.CMDUploadChange(options, [], 'foo', git_cl.ChangeDescription('bar')) @@ -256,10 +257,11 @@ class TestGitClBasic(unittest.TestCase): mock.patch('git_cl.Changelist.GetGerritProject', return_value='foo').start() mock.patch('git_cl.gerrit_util.GetProjectHead', - return_value='refs/heads/old_default').start() + return_value='refs/heads/master').start() cl = git_cl.Changelist() options = optparse.Values() + options.target_branch = 'refs/heads/master' with self.assertRaises(SystemExitMock): cl.CMDUploadChange(options, [], 'foo', git_cl.ChangeDescription('bar')) @@ -283,6 +285,33 @@ class TestGitClBasic(unittest.TestCase): cl = git_cl.Changelist() options = optparse.Values() + options.target_branch = 'refs/heads/master' + cl.CMDUploadChange(options, [], 'foo', git_cl.ChangeDescription('bar')) + # ensure upload is called twice + self.assertEqual(len(m.mock_calls), 2) + # option overrides on retry + self.assertEqual(options.force, True) + self.assertEqual(options.edit_description, False) + + def test_upload_to_old_default_retry_on_rollback(self): + """Test when default branch migration had to be rolled back to old name""" + m = mock.patch('git_cl.Changelist._CMDUploadChange', + side_effect=[git_cl.GitPushError(), None]).start() + mock.patch('git_cl.Changelist.GetRemoteBranch', + return_value=('foo', git_cl.DEFAULT_NEW_BRANCH)).start() + mock.patch('git_cl.Changelist.GetGerritProject', + return_value='foo').start() + mock.patch('git_cl.gerrit_util.GetProjectHead', + return_value='refs/heads/master').start() + # GetTargetRef returns new default branch since it has stale remote + # information. + mock.patch('git_cl.GetTargetRef', + return_value='refs/heads/main').start() + mock.patch('git_cl.RunGit').start() + + cl = git_cl.Changelist() + options = optparse.Values() + options.target_branch = 'refs/heads/master' cl.CMDUploadChange(options, [], 'foo', git_cl.ChangeDescription('bar')) # ensure upload is called twice self.assertEqual(len(m.mock_calls), 2)