Commit dfa44dae authored by Josip Sokcevic's avatar Josip Sokcevic Committed by LUCI CQ

Add git-migrate-default-branch

R=ehmaldonado@chromium.org

Change-Id: Ib43132afd914bc3ea66f8a634ce8ead9bf2d3b4b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2496064
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: 's avatarEdward Lesmes <ehmaldonado@chromium.org>
parent fc48704f
#!/usr/bin/env bash
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
. "$(type -P python_runner.sh)"
#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Migrate local repository onto new default branch."""
import fix_encoding
import gerrit_util
import git_common
import metrics
import scm
import sys
import logging
from six.moves import urllib
def GetGerritProject(remote_url):
"""Returns Gerrit project name based on remote git URL."""
if remote_url is None:
raise RuntimeError('can\'t detect Gerrit project.')
project = urllib.parse.urlparse(remote_url).path.strip('/')
if project.endswith('.git'):
project = project[:-len('.git')]
# *.googlesource.com hosts ensure that Git/Gerrit projects don't start with
# 'a/' prefix, because 'a/' prefix is used to force authentication in
# gitiles/git-over-https protocol. E.g.,
# https://chromium.googlesource.com/a/v8/v8 refers to the same repo/project
# as
# https://chromium.googlesource.com/v8/v8
if project.startswith('a/'):
project = project[len('a/'):]
return project
def GetGerritHost(git_host):
parts = git_host.split('.')
parts[0] = parts[0] + '-review'
return '.'.join(parts)
def main():
remote = git_common.run('remote')
# Use first remote as source of truth
remote = remote.split("\n")[0]
if not remote:
raise RuntimeError('Could not find any remote')
url = scm.GIT.GetConfig(git_common.repo_root(), 'remote.%s.url' % remote)
host = urllib.parse.urlparse(url).netloc
if not host:
raise RuntimeError('Could not find remote host')
project_head = gerrit_util.GetProjectHead(GetGerritHost(host),
GetGerritProject(url))
if project_head != 'refs/heads/main':
raise RuntimeError("The repository is not migrated yet.")
git_common.run('fetch', remote)
branches = git_common.get_branches_info(True)
if 'master' in branches:
logging.info("Migrating master branch...")
if 'main' in branches:
logging.info('You already have master and main branch, consider removing '
'master manually:\n'
' $ git branch -d master\n')
else:
git_common.run('branch', '-m', 'master', 'main')
branches = git_common.get_branches_info(True)
for name in branches:
branch = branches[name]
if not branch:
continue
if 'master' in branch.upstream:
logging.info("Migrating %s branch..." % name)
new_upstream = branch.upstream.replace('master', 'main')
git_common.run('branch', '--set-upstream-to', new_upstream, name)
git_common.remove_merge_base(name)
if __name__ == '__main__':
fix_encoding.fix_encoding()
logging.basicConfig(level=logging.INFO)
with metrics.collector.print_notice_and_exit():
try:
logging.info("Starting migration")
main()
logging.info("Migration completed")
except RuntimeError as e:
logging.error("Error %s" % str(e))
sys.exit(1)
#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for git_migrate_default_branch.py."""
import collections
import os
import sys
import unittest
if sys.version_info.major == 2:
import mock
else:
from unittest import mock
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import git_migrate_default_branch
class CMDFormatTestCase(unittest.TestCase):
def setUp(self):
self.addCleanup(mock.patch.stopall)
def test_no_remote(self):
def RunMock(*args):
if args[0] == 'remote':
return ''
self.fail('did not expect such run git command: %s' % args[0])
mock.patch('git_migrate_default_branch.git_common.run', RunMock).start()
with self.assertRaisesRegexp(RuntimeError, 'Could not find any remote'):
git_migrate_default_branch.main()
def test_migration_not_ready(self):
def RunMock(*args):
if args[0] == 'remote':
return 'origin\ngerrit'
raise Exception('Did not expect such run git command: %s' % args[0])
mock.patch('git_migrate_default_branch.git_common.run', RunMock).start()
mock.patch('git_migrate_default_branch.git_common.repo_root',
return_value='.').start()
mock.patch('git_migrate_default_branch.scm.GIT.GetConfig',
return_value='https://chromium.googlesource.com').start()
mock.patch('git_migrate_default_branch.gerrit_util.GetProjectHead',
return_value=None).start()
with self.assertRaisesRegexp(RuntimeError, 'not migrated yet'):
git_migrate_default_branch.main()
def test_migration_no_master(self):
def RunMock(*args):
if args[0] == 'remote':
return 'origin\ngerrit'
elif args[0] == 'fetch':
return
elif args[0] == 'branch':
return
raise Exception('Did not expect such run git command: %s' % args[0])
mock_runs = mock.patch('git_migrate_default_branch.git_common.run',
side_effect=RunMock).start()
mock.patch('git_migrate_default_branch.git_common.repo_root',
return_value='.').start()
mock.patch('git_migrate_default_branch.scm.GIT.GetConfig',
return_value='https://chromium.googlesource.com').start()
mock.patch('git_migrate_default_branch.gerrit_util.GetProjectHead',
return_value='refs/heads/main').start()
BranchesInfo = collections.namedtuple('BranchesInfo',
'hash upstream commits behind')
branches = {
'': None, # always returned
'master': BranchesInfo('0000', 'origin/master', '0', '0'),
'feature': BranchesInfo('0000', 'master', '0', '0'),
'another_feature': BranchesInfo('0000', 'feature', '0', '0'),
'remote_feature': BranchesInfo('0000', 'origin/master', '0', '0'),
}
mock.patch('git_migrate_default_branch.git_common.get_branches_info',
return_value=branches).start()
mock_merge_base = mock.patch(
'git_migrate_default_branch.git_common.remove_merge_base',
return_value=branches).start()
git_migrate_default_branch.main()
mock_merge_base.assert_any_call('feature')
mock_merge_base.assert_any_call('remote_feature')
mock_runs.assert_any_call('branch', '-m', 'master', 'main')
mock_runs.assert_any_call('branch', '--set-upstream-to', 'main', 'feature')
mock_runs.assert_any_call('branch', '--set-upstream-to', 'origin/main',
'remote_feature')
if __name__ == '__main__':
unittest.main()
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