aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiana Picus <diana.picus@linaro.org>2017-06-09 19:14:08 +0200
committerDiana Picus <diana.picus@linaro.org>2017-07-07 18:08:56 +0200
commitefc7bdaf3f4e53b0b9c15c4b1100dcf2e9d30b09 (patch)
tree50ab59c5106a23a2f11ee9478407f278afc520b4
parent3601bea50533d4ba06fafac16664183cdb250ab0 (diff)
[llvmpush] Add llvm push
Add support for pushing the current branch in all linked subprojects in a given config. If they are not on the same branch, an error is thrown. The branches are pushed as linaro-local/$user/$branch, where $branch is the local name of the branch and $user is the user as seen in LLVM_GITUSER (we might want to change to a more intelligent way to get the user in the future). We achieve this by adding a new method to LLVMSourceConfig: for_each_enabled, which takes an action and applies it to all the enabled subprojects. If an exception is thrown by the action, it is rethrown by the action as a RuntimeError specifying which project the action was being applied to. Note that this does not behave like a transaction - the action may have been applied to some of the projects already, and there's no way to undo it. It's also a bit difficult to tell which projects the action was applied on (in the future we should probably log that info). We also provide an action that simply pushes the current branch to origin with the updated name. This makes sure the remote branch will live in an isolated namespace where it won't conflict with other people's branches. We also update some of the internals of LLVMSourceConfig to throw exceptions if some of the subprojects that are linked are not worktrees on the same branch as LLVM. In the past they were just ignored, but that doesn't seem like a sane default anymore. Change-Id: I9c917658d65b5d0e7bad3310efce07a0fe5bce5e
-rw-r--r--modules/llvm.py86
-rw-r--r--scripts/llvm.py25
-rw-r--r--tests/cli/llvmtestcase.py29
-rw-r--r--tests/cli/testllvmprojects.py4
-rw-r--r--tests/cli/testllvmpush.py166
-rw-r--r--tests/unittests/testllvmsourceconfig.py85
-rw-r--r--tests/unittests/testpush.py200
7 files changed, 557 insertions, 38 deletions
diff --git a/modules/llvm.py b/modules/llvm.py
index 34cd02c..2e7c44f 100644
--- a/modules/llvm.py
+++ b/modules/llvm.py
@@ -1,4 +1,5 @@
import os
+import re
from linaropy.git.worktree import Worktree
@@ -146,6 +147,24 @@ class LLVMSourceConfig(object):
for subproj in remove:
self.__remove_subproject(subproj)
+ def for_each_enabled(self, action):
+ """Perform the given action for each enabled subproject including LLVM.
+
+ The action must be a callable object receiving the path to the
+ subproject's directory as its only parameter.
+
+ If the action throws an exception, it will be rethrown as a
+ RuntimeError. Note that this does not have transactional behaviour.
+ """
+ for subproj in self.get_enabled_subprojects():
+ try:
+ action(self.__get_subproj_cmake_path(subproj))
+ except Exception as exc:
+ raise RuntimeError("Error while processing {}".format(subproj)) from exc
+
+ # Visit LLVM last, in case getting the enabled subprojects errors out.
+ action(self.llvmSourceTree.repodir)
+
def __get_subproj_object(self, subprojName):
"""Get the LLVMSubproject object corresponding to subprojName."""
if not subprojName in list(self.subprojs.keys()):
@@ -160,18 +179,22 @@ class LLVMSourceConfig(object):
def __is_enabled(self, subprojName):
"""
Check if subproj is enabled in this configuration. A subproj is
- considered to be enabled if it is a worktree on the correct branch.
+ considered to be enabled if it is a worktree on the correct branch. If
+ a directory for the subproject exists but does not satisfy those
+ conditions, an EnvironmentError is thrown.
"""
- try:
- # If this succeeds, the subproject has already been added.
- existing = Worktree(self.proj,
- self.__get_subproj_cmake_path(subprojName))
- except EnvironmentError:
- # If it's not a worktree (for whatever reason), it's not enabled.
+ subprojPath = self.__get_subproj_cmake_path(subprojName)
+
+ if not os.path.isdir(subprojPath):
return False
- # If it is a worktree, but on the wrong branch, it is not enabled.
- return existing.getbranch() == self.llvmSourceTree.getbranch()
+ existing = Worktree(self.proj, subprojPath)
+
+ if existing.getbranch() != self.llvmSourceTree.getbranch():
+ raise EnvironmentError("{} is on branch {}, but should be on {}".format(
+ subprojName, existing.getbranch(), self.llvmSourceTree.getbranch()))
+
+ return True
# TODO: add_subproject, remove_subproject and is_enabled should live in
# another object (AddRemoveStrategy?) that would know what we want to add
@@ -222,3 +245,48 @@ class LLVMSourceConfig(object):
worktree = Worktree(self.proj, path)
worktree.clean(False)
+
+
+# FIXME: repo.pushToBranch doesn't work, because it doesn't handle remote
+# branches properly. Furthermore, there's no support for getting the remote URL,
+# so we need to resort to raw git commands. We may also consider moving the
+# functionality for parsing info out of the remote URL into the repo object.
+from sh import git
+from linaropy.cd import cd
+
+
+def get_user_from_remote(url):
+ """Get the username used as part of the remote URL, or None.
+
+ The remote URLs that we expect to see look like $protocol://$user@$location.
+ If they look any different, we won't be able to parse them.
+ """
+ pattern = re.compile("(.*://)?(?P<user>.*)@(.*)\n?")
+ match = pattern.match(str(url))
+ if match is None:
+ return None
+ return match.group('user')
+
+
+def push_current_branch(proj, pathToRepo):
+ """Push the current branch to origin.
+
+ It will be pushed into linaro-local/$user/$branch, where $branch is the
+ current branch and $user is the username used as part of the remote URL.
+ """
+ repo = Worktree(proj, pathToRepo)
+
+ with cd(repo.repodir):
+ remote = git("remote", "get-url", "--push", "origin").strip()
+ user = get_user_from_remote(remote)
+ if not user:
+ raise EnvironmentError("Couldn't parse user from {}.".format(remote))
+
+ local_branch = repo.getbranch()
+ remote_branch = "linaro-local/{}/{}".format(user, local_branch)
+ if not repo.is_valid_branch_name(remote_branch):
+ raise EnvironmentError(
+ "{} is not a valid branch name.".format(remote_branch))
+
+ with cd(repo.repodir):
+ git("push", "-u", "origin", "+{}:{}".format(local_branch, remote_branch))
diff --git a/scripts/llvm.py b/scripts/llvm.py
index 829c720..2252ab2 100644
--- a/scripts/llvm.py
+++ b/scripts/llvm.py
@@ -3,11 +3,12 @@
import os
from sys import exit
-from modules.llvm import LLVMSubproject, LLVMSourceConfig
+from modules.llvm import LLVMSubproject, LLVMSourceConfig, push_current_branch
from linaropy.git.clone import Clone
from linaropy.proj import Proj
from argparse import Action, ArgumentParser, RawTextHelpFormatter
+from functools import partial
def die(message, config_to_dump=None):
@@ -64,6 +65,22 @@ def projects(args):
dump_config(config)
+
+def push_branch(args):
+ """Push current branch to origin."""
+
+ proj = Proj()
+
+ llvm_worktree_root = get_worktree_root(args.env)
+ config = LLVMSourceConfig(proj, llvm_worktree_root)
+
+ llvmBranch = Clone(proj, llvm_worktree_root).getbranch()
+
+ try:
+ config.for_each_enabled(partial(push_current_branch, proj))
+ except RuntimeError as exc:
+ die("Failed to push branch because: " + str(exc) + str(exc.__cause__))
+
##########################################################################
# Command line parsing #
##########################################################################
@@ -106,5 +123,11 @@ projs.add_argument(
metavar='subproject',
help="Unlink given subprojects.")
+# Subcommand for pushing the current branch to origin
+push = subcommands.add_parser(
+ "push",
+ help="Push current branch to origin linaro-local/<user>/<branch>, for all linked subprojects.")
+push.set_defaults(run_command=push_branch)
+
args = options.parse_args()
args.run_command(args)
diff --git a/tests/cli/llvmtestcase.py b/tests/cli/llvmtestcase.py
index b2fc765..9b4ac3e 100644
--- a/tests/cli/llvmtestcase.py
+++ b/tests/cli/llvmtestcase.py
@@ -34,19 +34,28 @@ class LLVMTestCase(unittest.TestCase):
script = os.path.join("scripts", "llvm.py")
@classmethod
- def create_dummy_commit(cls):
- filename = "filethatshouldntexist"
+ def create_dummy_commit(cls, commitMessage="Dummy commit"):
+ filename = "filethatshouldntexist" + str(uuid4())
cls.run_quietly(["touch", filename])
- cls.run_quietly(["git", "add", filename])
- cls.run_quietly(["git", "commit", "-m", "Dummy commit"])
+ try:
+ cls.run_quietly(["git", "add", filename])
+ cls.run_quietly(["git", "commit", "-m", commitMessage])
+ except subprocess.CalledProcessError as exc:
+ print("Command {} exited with error code {}:\n{}".format(
+ exc.cmd, exc.returncode, exc.output))
@classmethod
- def create_dummy_repo(cls, repopath):
- if not os.path.isdir(repopath):
- os.makedirs(repopath)
+ def create_dummy_repo(cls, repopath, originpath=None):
+ if originpath is not None:
+ cls.run_quietly(["git", "clone", originpath, repopath])
+ else:
+ if not os.path.isdir(repopath):
+ os.makedirs(repopath)
+
+ with cd(repopath):
+ cls.run_quietly(["git", "init"])
with cd(repopath):
- cls.run_quietly(["git", "init"])
cls.create_dummy_commit()
@classmethod
@@ -55,10 +64,6 @@ class LLVMTestCase(unittest.TestCase):
cls.run_quietly(["git", "worktree", "add", worktreepath,
"-b", branch])
- @classmethod
- def get_subproj_repo(cls, subproj):
- return os.path.join(cls.repos, subproj)
-
@staticmethod
def run_with_output(*args, **kwargs):
"""Helper for running a command and capturing stdout and stderr"""
diff --git a/tests/cli/testllvmprojects.py b/tests/cli/testllvmprojects.py
index 394f3ac..30f7ff2 100644
--- a/tests/cli/testllvmprojects.py
+++ b/tests/cli/testllvmprojects.py
@@ -24,6 +24,10 @@ class Testllvmprojs(LLVMTestCase):
return cls.command_with_defaults("projects", *args, **kwargs)
@classmethod
+ def get_subproj_repo(cls, subproj):
+ return os.path.join(cls.repos, subproj)
+
+ @classmethod
def setUpClass(cls):
"""Create the file structure and environment that llvmprojs expects"""
cls.llvm_root = mkdtemp()
diff --git a/tests/cli/testllvmpush.py b/tests/cli/testllvmpush.py
new file mode 100644
index 0000000..769aa18
--- /dev/null
+++ b/tests/cli/testllvmpush.py
@@ -0,0 +1,166 @@
+"""Command line interface tests for llvm.py push.
+
+Note that although this uses the unittest framework, it does *not* contain unit
+tests.
+
+"""
+
+import shutil
+import os
+import subprocess
+import unittest
+
+from tempfile import mkdtemp
+from uuid import uuid4
+
+from linaropy.cd import cd
+from linaropy.git.clone import Clone
+from llvmtestcase import LLVMTestCase, debug
+
+
+class Testllvmpush(LLVMTestCase):
+
+ @classmethod
+ def llvm_push(cls, *args, **kwargs):
+ return cls.command_with_defaults("push", *args, **kwargs)
+
+ @classmethod
+ def get_origin_path(cls, subproj):
+ return os.path.join(cls.origins, subproj)
+
+ @classmethod
+ def get_clone_path(cls, subproj):
+ return os.path.join(cls.repos, subproj)
+
+ @classmethod
+ def setUpClass(cls):
+ """Create the file structure and environment for testing llvm push."""
+ cls.all_repos = ("llvm", "clang", "compiler-rt", "lld", "lldb",
+ "libcxx", "libcxxabi", "libunwind", "test-suite")
+
+ cls.origins = mkdtemp()
+ cls.repos = mkdtemp()
+ cls.llvm_root = mkdtemp()
+
+ cls.user = "llvm-developer"
+
+ # Create dummy repos - one origin that we will push to, and one clone
+ # that we will create worktrees from
+ for reponame in cls.all_repos:
+ origin = cls.get_origin_path(reponame)
+ cls.create_dummy_repo(origin)
+
+ clone = cls.get_clone_path(reponame)
+ cls.create_dummy_repo(
+ clone, "file://{}@{}".format(cls.user, origin))
+
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.origins)
+ shutil.rmtree(cls.repos)
+
+ @classmethod
+ def setUp(cls):
+ cls.env = os.path.join(cls.llvm_root, "env" + str(uuid4()))
+ cls.llvm_src = os.path.join(cls.env, "llvm")
+
+ # Create LLVM worktree
+ cls.branch = "br" + str(uuid4())
+ cls.add_worktree(cls.get_clone_path("llvm"), cls.llvm_src,
+ cls.branch)
+
+ @classmethod
+ def tearDown(cls):
+ # Clean up the directories where we might have added subprojects.
+ # This isn't 100% clean, because we don't clean up the repos between
+ # tests (so any branches will remain), but it's good enough for the
+ # current tests.
+ for subprojdir in (os.path.join(cls.llvm_src, "projects"),
+ os.path.join(cls.llvm_src, "tools")):
+ if os.path.isdir(subprojdir):
+ shutil.rmtree(subprojdir)
+ os.makedirs(subprojdir)
+
+ # Run prune on the original repos, to remove any dangling worktrees.
+ for reponame in cls.all_repos:
+ repopath = cls.get_clone_path(reponame)
+ with cd(repopath):
+ cls.run_quietly(["git", "worktree", "prune"])
+
+ def test_push(self):
+ with cd(self.llvm_src):
+ self.create_dummy_commit("Test llvm push")
+
+ enabled = ["clang", "lld"]
+
+ for subproj in enabled:
+ worktreePath = os.path.join(self.llvm_src, "tools", subproj)
+ self.add_worktree(self.get_clone_path(subproj),
+ worktreePath,
+ self.branch)
+ with cd(worktreePath):
+ self.create_dummy_commit("Test {} push".format(subproj))
+
+ self.run_quietly(self.llvm_push())
+ remote_branch = "linaro-local/{}/{}".format(self.user, self.branch)
+
+ for subproj in self.all_repos:
+ origin = self.get_origin_path(subproj)
+
+ with cd(origin):
+ if subproj == "llvm" or subproj in enabled:
+ output = self.run_with_output(["git", "log", "--oneline",
+ "-1", remote_branch])
+
+ self.assertRegex(
+ output, ".*Test {} push.*".format(subproj))
+ else:
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ output = self.run_with_output(
+ ["git", "log", "--oneline", "-1", remote_branch])
+
+ self.assertRegex(
+ str(context.exception.output),
+ "(.*\n)*.*unknown revision or path not in the working tree(.*\n)*")
+
+ def test_push_mismatched_branches(self):
+ with cd(self.llvm_src):
+ self.create_dummy_commit("Test llvm push")
+
+ enabled = ["clang", "lld"]
+
+ for subproj in enabled:
+ branch = self.branch
+
+ # Move only lld to a different branch
+ if subproj == "lld":
+ branch = branch + "-different"
+
+ worktreePath = os.path.join(self.llvm_src, "tools", subproj)
+ self.add_worktree(self.get_clone_path(subproj),
+ worktreePath,
+ branch)
+ with cd(worktreePath):
+ self.create_dummy_commit("Test {} push".format(subproj))
+
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ output = self.run_with_output(self.llvm_push())
+
+ # Check that we error out because lld is on a different branch
+ self.assertRegex(str(context.exception.output),
+ "(.*\n)*.*lld is on branch {}, but should be on {}".format(
+ self.branch + "-different", self.branch))
+
+ # Check that we haven't pushed llvm or clang either, even if they're on
+ # the same branch
+ remote_branch = "linaro-local/{}/{}".format(self.user, self.branch)
+
+ for subproj in ["llvm", "clang"]:
+ with cd(self.get_origin_path(subproj)):
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ self.run_with_output(["git", "log", "--oneline", "-1",
+ remote_branch])
+
+ self.assertRegex(
+ str(context.exception.output),
+ "(.*\n)*.*unknown revision or path not in the working tree(.*\n)*")
diff --git a/tests/unittests/testllvmsourceconfig.py b/tests/unittests/testllvmsourceconfig.py
index 3ad93b1..8a60065 100644
--- a/tests/unittests/testllvmsourceconfig.py
+++ b/tests/unittests/testllvmsourceconfig.py
@@ -3,6 +3,7 @@ import unittest
import uuid
from sh import git
+from unittest.mock import MagicMock, call
from linaropy.cd import cd
from linaropy.proj import Proj
@@ -130,13 +131,12 @@ class TestLLVMSourceConfig(unittest.TestCase):
os.makedirs(path)
config = LLVMSourceConfig(self.proj, sourcePath)
- enabled = config.get_enabled_subprojects()
+ with self.assertRaises(EnvironmentError) as context:
+ config.get_enabled_subprojects()
- # Check that if it's not a worktree, it's not enabled.
- self.assertEqual(
- enabled,
- [],
- "Detected unexpected projects %s" % str(enabled)
+ self.assertRegex(
+ str(context.exception),
+ ".*compiler-rt is not a worktree.*"
)
def test_detect_enabled_wrong_branch(self):
@@ -153,14 +153,12 @@ class TestLLVMSourceConfig(unittest.TestCase):
self.assertTrue(os.path.isdir(path), "Failed to create worktree")
config = LLVMSourceConfig(self.proj, sourcePath)
- enabled = config.get_enabled_subprojects()
+ with self.assertRaises(EnvironmentError) as context:
+ config.get_enabled_subprojects()
- # Check that if it's a worktree on the wrong branch, it's not enabled.
- self.assertEqual(
- enabled,
- [],
- "Detected unexpected projects %s" % str(enabled)
- )
+ self.assertRegex(str(context.exception),
+ "compiler-rt is on branch {}, but should be on {}".format(branch,
+ self.temporaryLLVMbranch))
def test_add_invalid_subproject(self):
config = LLVMSourceConfig(self.proj, self.temporaryLLVM.repodir)
@@ -284,7 +282,7 @@ class TestLLVMSourceConfig(unittest.TestCase):
config.update({ "lldb" : self.__get_subproj_repo("lldb")})
self.assertRegex(str(context.exception),
- "{} already exists but is not a valid subproject directory.*"
+ "{} is not a worktree.*"
.format(existingPath))
# If we got this far, we're probably ok, but let's be pedantic and check
@@ -314,8 +312,8 @@ class TestLLVMSourceConfig(unittest.TestCase):
config.update({ "lldb" : self.__get_subproj_repo("lldb")})
self.assertRegex(str(context.exception),
- "{} already exists but is not a valid subproject directory.*"
- .format(existingPath))
+ "lldb is on branch {}, but should be on {}.*"
+ .format(branch, self.temporaryLLVMbranch))
# If we got this far, we're probably ok, but let's be pedantic and check
# that the subproject is still there
@@ -568,5 +566,60 @@ class TestLLVMSourceConfig(unittest.TestCase):
# TODO: test that CMake gets our layout
+ def test_for_each_enabled(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ logPath = unittest.mock.MagicMock()
+ config.for_each_enabled(logPath)
+
+ logPath.assert_called_with(self.temporaryLLVM.repodir)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ enabled = ["clang", "compiler-rt", "lld", "lldb", "libunwind"]
+ calls = []
+
+ for subproj in enabled:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ calls.append(call(path)) # Expect our mock to be called with path
+
+ logPath = unittest.mock.MagicMock()
+ config.for_each_enabled(logPath)
+
+ logPath.assert_has_calls(calls, any_order=True)
+
+ def _throw(self, projPath):
+ if "lld" in projPath:
+ raise ValueError("An error has been!!1")
+
+ def test_for_each_enabled_error(self):
+ """Test that we rethrow exceptions correctly."""
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ subprojs = LLVMSubproject.get_all_subprojects()
+ enabled = ["clang", "compiler-rt", "lld", "lldb", "libunwind"]
+
+ for subproj in enabled:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ with self.assertRaises(RuntimeError) as context:
+ config.for_each_enabled(self._throw)
+ self.assertRegex(str(context.exception),
+ "(.*\n?)*Error while processing lld(.*\n)*")
+ self.assertRegex(str(context.exception.__cause__),
+ "(.*\n?)*An error has been!!1(.*\n)*")
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unittests/testpush.py b/tests/unittests/testpush.py
new file mode 100644
index 0000000..0046e2b
--- /dev/null
+++ b/tests/unittests/testpush.py
@@ -0,0 +1,200 @@
+import os
+import unittest
+import uuid
+
+from sh import git
+
+from linaropy.cd import cd
+from linaropy.proj import Proj
+from linaropy.git.clone import Clone
+from linaropy.git.worktree import Worktree
+
+from modules.llvm import get_user_from_remote, push_current_branch
+
+
+class TestGetUser(unittest.TestCase):
+ testdirprefix = "GetUserUT"
+
+ def test_full_remote(self):
+ user = get_user_from_remote("https://user1@remote.url:1234")
+ self.assertEqual(user, "user1")
+
+ user = get_user_from_remote("ssh://user2@remote.url:1234")
+ self.assertEqual(user, "user2")
+
+ def test_no_protocol_remote(self):
+ user = get_user_from_remote("user.name@remote.url:1234")
+ self.assertEqual(user, "user.name")
+
+ def test_no_user(self):
+ user = get_user_from_remote("https://remote.url:1234:")
+ self.assertEqual(user, None)
+
+
+class TestPush(unittest.TestCase):
+ testdirprefix = "PushUT"
+
+ def __create_dummy_commit(
+ self,
+ message="Branches without commits confuse git"):
+ filename = "file" + str(uuid.uuid4())
+ open(filename, "a").close()
+ git("add", filename)
+ git("commit", "-m", message)
+
+ def __get_last_commit_message(self, branch=""):
+ return str(git("rev-list", "-1", "--oneline", branch)).strip()
+
+ def __create_dummy_repo(self, path):
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ with cd(path):
+ git("init")
+ self.__create_dummy_commit()
+
+ def setUp(self):
+ # We're going to create a hierarchy with an origin repo, a clone repo
+ # and a worktree of the clone. When pushing through the worktree, we
+ # expect to see things in origin.
+ self.proj = Proj(prefix=self.testdirprefix)
+ self.user = "llvm-developer"
+
+ path = os.path.join(self.proj.projdir, "origin")
+ self.__create_dummy_repo(path)
+ self.origin = Clone(self.proj, path)
+
+ path = os.path.join(self.proj.projdir, "clone")
+ git("clone", "file://" + self.user + "@" + self.origin.repodir, path)
+ self.clone = Clone(self.proj, path)
+
+ self.worktreeBranch = "a-branch"
+ self.worktree = Worktree.create(
+ self.proj, self.clone, os.path.join(
+ self.proj.projdir, "worktree"), self.worktreeBranch)
+
+ def tearDown(self):
+ self.proj.cleanup()
+
+ def test_push_new_branch(self):
+ """Test that we can push a new branch to origin."""
+
+ with cd(self.worktree.repodir):
+ self.__create_dummy_commit("This should make it to origin")
+
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ with cd(self.origin.repodir):
+ self.assertRegex(
+ self.__get_last_commit_message(
+ "linaro-local/{}/{}".format(
+ self.user,
+ self.worktreeBranch)),
+ ".*This should make it to origin")
+
+ def test_push_existing_branch(self):
+ """Test that we can push to a branch that already exists in origin."""
+
+ remoteBranch = "linaro-local/{}/{}".format(
+ self.user, self.worktreeBranch)
+
+ with cd(self.worktree.repodir):
+ self.__create_dummy_commit("This already exists in origin")
+ git("push", "origin", "{}:{}".format(
+ self.worktreeBranch, remoteBranch))
+
+ self.__create_dummy_commit("This should make it to origin too")
+
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ with cd(self.origin.repodir):
+ self.assertRegex(self.__get_last_commit_message(remoteBranch),
+ ".*This should make it to origin too")
+
+ def test_push_squashed_update(self):
+ """
+ Test that we can push again after squashing some fixes into a commit
+ that has already been pushed to origin. This isn't a nice thing to do,
+ but we need to support it because Gerrit requires squashes.
+ """
+ remoteBranch = "linaro-local/{}/{}".format(self.user,
+ self.worktreeBranch)
+ with cd(self.worktree.repodir):
+ self.__create_dummy_commit("First version of the patch")
+
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ with cd(self.origin.repodir):
+ self.assertRegex(
+ self.__get_last_commit_message(remoteBranch),
+ ".*First version of the patch")
+
+ with cd(self.worktree.repodir):
+ git("commit", "--amend", "-m", "Second version of the patch")
+
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ with cd(self.origin.repodir):
+ self.assertRegex(
+ self.__get_last_commit_message(remoteBranch),
+ "/*Second version of the patch")
+
+ def test_push_rebased_branch(self):
+ """ Test that we can push again with new updates after a rebase."""
+ remoteBranch = "linaro-local/{}/{}".format(self.user,
+ self.worktreeBranch)
+ with cd(self.worktree.repodir):
+ self.__create_dummy_commit("First change")
+
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ with cd(self.origin.repodir):
+ self.assertRegex(
+ self.__get_last_commit_message(remoteBranch),
+ ".*First change")
+
+ with cd(self.origin.repodir):
+ self.__create_dummy_commit("Master moving forward")
+
+ with cd(self.worktree.repodir):
+ self.__create_dummy_commit("Second change")
+ git("fetch", "origin", "master")
+ git("rebase", "origin/master")
+
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ with cd(self.origin.repodir):
+ self.assertRegex(self.__get_last_commit_message(remoteBranch),
+ ".*Second change")
+
+ def test_push_no_user(self):
+ """Test that we error out when we can't parse a user in the remote name."""
+ remote = "file://" + self.origin.repodir
+ with cd(self.worktree.repodir):
+ git("remote", "set-url", "origin", remote)
+ self.__create_dummy_commit("Make it look like a real branch")
+
+ with self.assertRaises(EnvironmentError) as context:
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ self.assertEqual(str(context.exception),
+ "Couldn't parse user from {}.".format(remote))
+
+ def test_push_invalid_user(self):
+ """Test that we error out when the value of LLVM_GITUSER wouldn't look good in a branch name (e.g. if it contains spaces)."""
+
+ badUser = "LLVM Developer"
+ with cd(self.worktree.repodir):
+ git("remote", "set-url", "origin",
+ "file://{}@{}".format(badUser, self.origin.repodir))
+ self.__create_dummy_commit("Make it look like a real branch")
+
+ with self.assertRaises(EnvironmentError) as context:
+ push_current_branch(self.proj, self.worktree.repodir)
+
+ self.assertEqual(str(context.exception),
+ "linaro-local/{}/{} is not a valid branch name.".format(badUser,
+ self.worktreeBranch))
+
+if __name__ == "__main__":
+ unittest.main()