123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017 |
- # -*- coding: utf-8 -*-
- """
- Tests for the Git state
- """
- from __future__ import absolute_import, print_function, unicode_literals
- import functools
- import inspect
- import logging
- import os
- import shutil
- import socket
- import string
- import tempfile
- import salt.utils.files
- import salt.utils.path
- from salt.ext.six.moves.urllib.parse import ( # pylint: disable=no-name-in-module
- urlparse,
- )
- from salt.utils.versions import LooseVersion as _LooseVersion
- from tests.support.case import ModuleCase
- from tests.support.helpers import TstSuiteLoggingHandler, slowTest, with_tempdir
- from tests.support.mixins import SaltReturnAssertsMixin
- from tests.support.runtests import RUNTIME_VARS
- TEST_REPO = "https://github.com/saltstack/salt-test-repo.git"
- def __check_git_version(caller, min_version, skip_msg):
- """
- Common logic for version check
- """
- if inspect.isclass(caller):
- actual_setup = getattr(caller, "setUp", None)
- def setUp(self, *args, **kwargs):
- if not salt.utils.path.which("git"):
- self.skipTest("git is not installed")
- git_version = self.run_function("git.version")
- if _LooseVersion(git_version) < _LooseVersion(min_version):
- self.skipTest(skip_msg.format(min_version, git_version))
- if actual_setup is not None:
- actual_setup(self, *args, **kwargs)
- caller.setUp = setUp
- return caller
- @functools.wraps(caller)
- def wrapper(self, *args, **kwargs):
- if not salt.utils.path.which("git"):
- self.skipTest("git is not installed")
- git_version = self.run_function("git.version")
- if _LooseVersion(git_version) < _LooseVersion(min_version):
- self.skipTest(skip_msg.format(min_version, git_version))
- return caller(self, *args, **kwargs)
- return wrapper
- def ensure_min_git(caller=None, min_version="1.6.5"):
- """
- Skip test if minimum supported git version is not installed
- """
- if caller is None:
- return functools.partial(ensure_min_git, min_version=min_version)
- return __check_git_version(
- caller, min_version, "git {0} or newer required to run this test (detected {1})"
- )
- def uses_git_opts(caller):
- """
- Skip test if git_opts is not supported
- IMPORTANT! This decorator should be at the bottom of any decorators added
- to a given function.
- """
- min_version = "1.7.2"
- return __check_git_version(
- caller,
- min_version,
- "git_opts only supported in git {0} and newer (detected {1})",
- )
- class WithGitMirror(object):
- def __init__(self, repo_url, **kwargs):
- self.repo_url = repo_url
- if "dir" not in kwargs:
- kwargs["dir"] = RUNTIME_VARS.TMP
- self.kwargs = kwargs
- def __call__(self, func):
- self.func = func
- return functools.wraps(func)(
- # pylint: disable=unnecessary-lambda
- lambda testcase, *args, **kwargs: self.wrap(testcase, *args, **kwargs)
- # pylint: enable=unnecessary-lambda
- )
- def wrap(self, testcase, *args, **kwargs):
- # Get temp dir paths
- mirror_dir = tempfile.mkdtemp(**self.kwargs)
- admin_dir = tempfile.mkdtemp(**self.kwargs)
- clone_dir = tempfile.mkdtemp(**self.kwargs)
- # Clean up the directories, we want git to actually create them
- os.rmdir(mirror_dir)
- os.rmdir(admin_dir)
- os.rmdir(clone_dir)
- # Create a URL to clone
- mirror_url = "file://" + mirror_dir
- # Mirror the repo
- testcase.run_function("git.clone", [mirror_dir], url=TEST_REPO, opts="--mirror")
- # Make sure the directory for the mirror now exists
- assert os.path.exists(mirror_dir)
- # Clone to the admin dir
- ret = testcase.run_state("git.latest", name=mirror_url, target=admin_dir)
- ret = ret[next(iter(ret))]
- assert os.path.exists(admin_dir)
- try:
- # Run the actual function with three arguments added:
- # 1. URL for the test to use to clone
- # 2. Cloned admin dir for making/pushing changes to the mirror
- # 3. Yet-nonexistent clone_dir for the test function to use as a
- # destination for cloning.
- return self.func(
- testcase, mirror_url, admin_dir, clone_dir, *args, **kwargs
- )
- finally:
- shutil.rmtree(mirror_dir, ignore_errors=True)
- shutil.rmtree(admin_dir, ignore_errors=True)
- shutil.rmtree(clone_dir, ignore_errors=True)
- with_git_mirror = WithGitMirror
- @ensure_min_git
- class GitTest(ModuleCase, SaltReturnAssertsMixin):
- """
- Validate the git state
- """
- def setUp(self):
- domain = urlparse(TEST_REPO).netloc
- try:
- if hasattr(socket, "setdefaulttimeout"):
- # 10 second dns timeout
- socket.setdefaulttimeout(10)
- socket.gethostbyname(domain)
- except socket.error:
- msg = "error resolving {0}, possible network issue?"
- self.skipTest(msg.format(domain))
- def tearDown(self):
- # Reset the dns timeout after the test is over
- socket.setdefaulttimeout(None)
- def _head(self, cwd):
- return self.run_function("git.rev_parse", [cwd, "HEAD"])
- @with_tempdir(create=False)
- @slowTest
- def test_latest(self, target):
- """
- git.latest
- """
- ret = self.run_state("git.latest", name=TEST_REPO, target=target)
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- @with_tempdir(create=False)
- @slowTest
- def test_latest_config_get_regexp_retcode(self, target):
- """
- git.latest
- """
- log_format = "[%(levelname)-8s] %(jid)s %(message)s"
- self.handler = TstSuiteLoggingHandler(format=log_format, level=logging.DEBUG)
- ret_code_err = "failed with return code: 1"
- with self.handler:
- ret = self.run_state("git.latest", name=TEST_REPO, target=target)
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- assert any(ret_code_err in s for s in self.handler.messages) is False, False
- @with_tempdir(create=False)
- @slowTest
- def test_latest_with_rev_and_submodules(self, target):
- """
- git.latest
- """
- ret = self.run_state(
- "git.latest", name=TEST_REPO, rev="develop", target=target, submodules=True
- )
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- @with_tempdir(create=False)
- @slowTest
- def test_latest_failure(self, target):
- """
- git.latest
- """
- ret = self.run_state(
- "git.latest",
- name="https://youSpelledGitHubWrong.com/saltstack/salt-test-repo.git",
- rev="develop",
- target=target,
- submodules=True,
- )
- self.assertSaltFalseReturn(ret)
- self.assertFalse(os.path.isdir(os.path.join(target, ".git")))
- @with_tempdir()
- @slowTest
- def test_latest_empty_dir(self, target):
- """
- git.latest
- """
- ret = self.run_state(
- "git.latest", name=TEST_REPO, rev="develop", target=target, submodules=True
- )
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- @with_tempdir(create=False)
- @slowTest
- def test_latest_unless_no_cwd_issue_6800(self, target):
- """
- cwd=target was being passed to _run_check which blew up if
- target dir did not already exist.
- """
- ret = self.run_state(
- "git.latest",
- name=TEST_REPO,
- rev="develop",
- target=target,
- unless="test -e {0}".format(target),
- submodules=True,
- )
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- @with_tempdir(create=False)
- @slowTest
- def test_numeric_rev(self, target):
- """
- git.latest with numeric revision
- """
- ret = self.run_state(
- "git.latest",
- name=TEST_REPO,
- rev=0.11,
- target=target,
- submodules=True,
- timeout=120,
- )
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- @with_tempdir(create=False)
- @slowTest
- def test_latest_with_local_changes(self, target):
- """
- Ensure that we fail the state when there are local changes and succeed
- when force_reset is True.
- """
- # Clone repo
- ret = self.run_state("git.latest", name=TEST_REPO, target=target)
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- # Make change to LICENSE file.
- with salt.utils.files.fopen(os.path.join(target, "LICENSE"), "a") as fp_:
- fp_.write("Lorem ipsum dolor blah blah blah....\n")
- # Make sure that we now have uncommitted changes
- self.assertTrue(self.run_function("git.diff", [target, "HEAD"]))
- # Re-run state with force_reset=False
- ret = self.run_state(
- "git.latest", name=TEST_REPO, target=target, force_reset=False
- )
- self.assertSaltTrueReturn(ret)
- self.assertEqual(
- ret[next(iter(ret))]["comment"],
- (
- "Repository {0} is up-to-date, but with uncommitted changes. "
- "Set 'force_reset' to True to purge uncommitted changes.".format(target)
- ),
- )
- # Now run the state with force_reset=True
- ret = self.run_state(
- "git.latest", name=TEST_REPO, target=target, force_reset=True
- )
- self.assertSaltTrueReturn(ret)
- # Make sure that we no longer have uncommitted changes
- self.assertFalse(self.run_function("git.diff", [target, "HEAD"]))
- @with_git_mirror(TEST_REPO)
- @uses_git_opts
- @slowTest
- def test_latest_fast_forward(self, mirror_url, admin_dir, clone_dir):
- """
- Test running git.latest state a second time after changes have been
- made to the remote repo.
- """
- # Clone the repo
- ret = self.run_state("git.latest", name=mirror_url, target=clone_dir)
- ret = ret[next(iter(ret))]
- assert ret["result"]
- # Make a change to the repo by editing the file in the admin copy
- # of the repo and committing.
- head_pre = self._head(admin_dir)
- with salt.utils.files.fopen(os.path.join(admin_dir, "LICENSE"), "a") as fp_:
- fp_.write("Hello world!")
- self.run_function(
- "git.commit",
- [admin_dir, "added a line"],
- git_opts='-c user.name="Foo Bar" -c user.email=foo@bar.com',
- opts="-a",
- )
- # Make sure HEAD is pointing to a new SHA so we know we properly
- # committed our change.
- head_post = self._head(admin_dir)
- assert head_pre != head_post
- # Push the change to the mirror
- # NOTE: the test will fail if the salt-test-repo's default branch
- # is changed.
- self.run_function("git.push", [admin_dir, "origin", "develop"])
- # Re-run the git.latest state on the clone_dir
- ret = self.run_state("git.latest", name=mirror_url, target=clone_dir)
- ret = ret[next(iter(ret))]
- assert ret["result"]
- # Make sure that the clone_dir now has the correct SHA
- assert head_post == self._head(clone_dir)
- @with_tempdir(create=False)
- def _changed_local_branch_helper(self, target, rev, hint):
- """
- We're testing two almost identical cases, the only thing that differs
- is the rev used for the git.latest state.
- """
- # Clone repo
- ret = self.run_state("git.latest", name=TEST_REPO, rev=rev, target=target)
- self.assertSaltTrueReturn(ret)
- # Check out a new branch in the clone and make a commit, to ensure
- # that when we re-run the state, it is not a fast-forward change
- self.run_function("git.checkout", [target, "new_branch"], opts="-b")
- with salt.utils.files.fopen(os.path.join(target, "foo"), "w"):
- pass
- self.run_function("git.add", [target, "."])
- self.run_function(
- "git.commit",
- [target, "add file"],
- git_opts='-c user.name="Foo Bar" -c user.email=foo@bar.com',
- )
- # Re-run the state, this should fail with a specific hint in the
- # comment field.
- ret = self.run_state("git.latest", name=TEST_REPO, rev=rev, target=target)
- self.assertSaltFalseReturn(ret)
- comment = ret[next(iter(ret))]["comment"]
- self.assertTrue(hint in comment)
- @uses_git_opts
- @slowTest
- def test_latest_changed_local_branch_rev_head(self):
- """
- Test for presence of hint in failure message when the local branch has
- been changed and a the rev is set to HEAD
- This test will fail if the default branch for the salt-test-repo is
- ever changed.
- """
- self._changed_local_branch_helper( # pylint: disable=no-value-for-parameter
- "HEAD",
- "The default remote branch (develop) differs from the local "
- "branch (new_branch)",
- )
- @uses_git_opts
- @slowTest
- def test_latest_changed_local_branch_rev_develop(self):
- """
- Test for presence of hint in failure message when the local branch has
- been changed and a non-HEAD rev is specified
- """
- self._changed_local_branch_helper( # pylint: disable=no-value-for-parameter
- "develop",
- "The desired rev (develop) differs from the name of the local "
- "branch (new_branch)",
- )
- @uses_git_opts
- @with_tempdir(create=False)
- @with_tempdir()
- @slowTest
- def test_latest_updated_remote_rev(self, name, target):
- """
- Ensure that we don't exit early when checking for a fast-forward
- """
- # Initialize a new git repository
- self.run_function("git.init", [name])
- # Add and commit a file
- with salt.utils.files.fopen(os.path.join(name, "foo.txt"), "w") as fp_:
- fp_.write("Hello world\n")
- self.run_function("git.add", [name, "."])
- self.run_function(
- "git.commit",
- [name, "initial commit"],
- git_opts='-c user.name="Foo Bar" -c user.email=foo@bar.com',
- )
- # Run the state to clone the repo we just created
- ret = self.run_state("git.latest", name=name, target=target,)
- self.assertSaltTrueReturn(ret)
- # Add another commit
- with salt.utils.files.fopen(os.path.join(name, "foo.txt"), "w") as fp_:
- fp_.write("Added a line\n")
- self.run_function(
- "git.commit",
- [name, "added a line"],
- git_opts='-c user.name="Foo Bar" -c user.email=foo@bar.com',
- opts="-a",
- )
- # Run the state again. It should pass, if it doesn't then there was
- # a problem checking whether or not the change is a fast-forward.
- ret = self.run_state("git.latest", name=name, target=target,)
- self.assertSaltTrueReturn(ret)
- @with_tempdir(create=False)
- @slowTest
- def test_latest_depth(self, target):
- """
- Test running git.latest state using the "depth" argument to limit the
- history. See #45394.
- """
- ret = self.run_state(
- "git.latest", name=TEST_REPO, rev="HEAD", target=target, depth=1
- )
- # HEAD is not a branch, this should fail
- self.assertSaltFalseReturn(ret)
- self.assertIn(
- "must be set to the name of a branch", ret[next(iter(ret))]["comment"]
- )
- ret = self.run_state(
- "git.latest",
- name=TEST_REPO,
- rev="non-default-branch",
- target=target,
- depth=1,
- )
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isdir(os.path.join(target, ".git")))
- @with_git_mirror(TEST_REPO)
- @uses_git_opts
- @slowTest
- def test_latest_sync_tags(self, mirror_url, admin_dir, clone_dir):
- """
- Test that a removed tag is properly reported as such and removed in the
- local clone, and that new tags are reported as new.
- """
- tag1 = "mytag1"
- tag2 = "mytag2"
- # Add and push a tag
- self.run_function("git.tag", [admin_dir, tag1])
- self.run_function("git.push", [admin_dir, "origin", tag1])
- # Clone the repo
- ret = self.run_state("git.latest", name=mirror_url, target=clone_dir)
- ret = ret[next(iter(ret))]
- assert ret["result"]
- # Now remove the tag
- self.run_function("git.push", [admin_dir, "origin", ":{0}".format(tag1)])
- # Add and push another tag
- self.run_function("git.tag", [admin_dir, tag2])
- self.run_function("git.push", [admin_dir, "origin", tag2])
- # Re-run the state with sync_tags=False. This should NOT delete the tag
- # from the local clone, but should report that a tag has been added.
- ret = self.run_state(
- "git.latest", name=mirror_url, target=clone_dir, sync_tags=False
- )
- ret = ret[next(iter(ret))]
- assert ret["result"]
- # Make ABSOLUTELY SURE both tags are present, since we shouldn't have
- # removed tag1.
- all_tags = self.run_function("git.list_tags", [clone_dir])
- assert tag1 in all_tags
- assert tag2 in all_tags
- # Make sure the reported changes are correct
- expected_changes = {"new_tags": [tag2]}
- assert ret["changes"] == expected_changes, ret["changes"]
- # Re-run the state with sync_tags=True. This should remove the local
- # tag, since it doesn't exist in the remote repository.
- ret = self.run_state(
- "git.latest", name=mirror_url, target=clone_dir, sync_tags=True
- )
- ret = ret[next(iter(ret))]
- assert ret["result"]
- # Make ABSOLUTELY SURE the expected tags are present/gone
- all_tags = self.run_function("git.list_tags", [clone_dir])
- assert tag1 not in all_tags
- assert tag2 in all_tags
- # Make sure the reported changes are correct
- expected_changes = {"deleted_tags": [tag1]}
- assert ret["changes"] == expected_changes, ret["changes"]
- @with_tempdir(create=False)
- @slowTest
- def test_cloned(self, target):
- """
- Test git.cloned state
- """
- # Test mode
- ret = self.run_state("git.cloned", name=TEST_REPO, target=target, test=True)
- ret = ret[next(iter(ret))]
- assert ret["result"] is None
- assert ret["changes"] == {"new": "{0} => {1}".format(TEST_REPO, target)}
- assert ret["comment"] == "{0} would be cloned to {1}".format(TEST_REPO, target)
- # Now actually run the state
- ret = self.run_state("git.cloned", name=TEST_REPO, target=target)
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert ret["changes"] == {"new": "{0} => {1}".format(TEST_REPO, target)}
- assert ret["comment"] == "{0} cloned to {1}".format(TEST_REPO, target)
- # Run the state again to test idempotence
- ret = self.run_state("git.cloned", name=TEST_REPO, target=target)
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert not ret["changes"]
- assert ret["comment"] == "Repository already exists at {0}".format(target)
- # Run the state again to test idempotence (test mode)
- ret = self.run_state("git.cloned", name=TEST_REPO, target=target, test=True)
- ret = ret[next(iter(ret))]
- assert not ret["changes"]
- assert ret["result"] is True
- assert ret["comment"] == "Repository already exists at {0}".format(target)
- @with_tempdir(create=False)
- @slowTest
- def test_cloned_with_branch(self, target):
- """
- Test git.cloned state with branch provided
- """
- old_branch = "master"
- new_branch = "develop"
- bad_branch = "thisbranchdoesnotexist"
- # Test mode
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=old_branch, test=True
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is None
- assert ret["changes"] == {"new": "{0} => {1}".format(TEST_REPO, target)}
- assert ret["comment"] == (
- "{0} would be cloned to {1} with branch '{2}'".format(
- TEST_REPO, target, old_branch
- )
- )
- # Now actually run the state
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=old_branch
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert ret["changes"] == {"new": "{0} => {1}".format(TEST_REPO, target)}
- assert ret["comment"] == (
- "{0} cloned to {1} with branch '{2}'".format(TEST_REPO, target, old_branch)
- )
- # Run the state again to test idempotence
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=old_branch
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert not ret["changes"]
- assert ret["comment"] == (
- "Repository already exists at {0} "
- "and is checked out to branch '{1}'".format(target, old_branch)
- )
- # Run the state again to test idempotence (test mode)
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, test=True, branch=old_branch
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert not ret["changes"]
- assert ret["comment"] == (
- "Repository already exists at {0} "
- "and is checked out to branch '{1}'".format(target, old_branch)
- )
- # Change branch (test mode)
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=new_branch, test=True
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is None
- assert ret["changes"] == {"branch": {"old": old_branch, "new": new_branch}}
- assert ret["comment"] == "Branch would be changed to '{0}'".format(new_branch)
- # Now really change the branch
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=new_branch
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert ret["changes"] == {"branch": {"old": old_branch, "new": new_branch}}
- assert ret["comment"] == "Branch changed to '{0}'".format(new_branch)
- # Change back to original branch. This tests that we don't attempt to
- # checkout a new branch (i.e. git checkout -b) for a branch that exists
- # locally, as that would fail.
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=old_branch
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is True
- assert ret["changes"] == {"branch": {"old": new_branch, "new": old_branch}}
- assert ret["comment"] == "Branch changed to '{0}'".format(old_branch)
- # Test switching to a nonexistent branch. This should fail.
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=bad_branch
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is False
- assert not ret["changes"]
- assert ret["comment"].startswith(
- "Failed to change branch to '{0}':".format(bad_branch)
- )
- @with_tempdir(create=False)
- @ensure_min_git(min_version="1.7.10")
- @slowTest
- def test_cloned_with_nonexistant_branch(self, target):
- """
- Test git.cloned state with a nonexistent branch provided
- """
- branch = "thisbranchdoesnotexist"
- # Test mode
- ret = self.run_state(
- "git.cloned", name=TEST_REPO, target=target, branch=branch, test=True
- )
- ret = ret[next(iter(ret))]
- assert ret["result"] is None
- assert ret["changes"]
- assert ret["comment"] == (
- "{0} would be cloned to {1} with branch '{2}'".format(
- TEST_REPO, target, branch
- )
- )
- # Now actually run the state
- ret = self.run_state("git.cloned", name=TEST_REPO, target=target, branch=branch)
- ret = ret[next(iter(ret))]
- assert ret["result"] is False
- assert not ret["changes"]
- assert ret["comment"].startswith("Clone failed:")
- assert "not found in upstream origin" in ret["comment"]
- @with_tempdir(create=False)
- @slowTest
- def test_present(self, name):
- """
- git.present
- """
- ret = self.run_state("git.present", name=name, bare=True)
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isfile(os.path.join(name, "HEAD")))
- @with_tempdir()
- @slowTest
- def test_present_failure(self, name):
- """
- git.present
- """
- fname = os.path.join(name, "stoptheprocess")
- with salt.utils.files.fopen(fname, "a"):
- pass
- ret = self.run_state("git.present", name=name, bare=True)
- self.assertSaltFalseReturn(ret)
- self.assertFalse(os.path.isfile(os.path.join(name, "HEAD")))
- @with_tempdir()
- @slowTest
- def test_present_empty_dir(self, name):
- """
- git.present
- """
- ret = self.run_state("git.present", name=name, bare=True)
- self.assertSaltTrueReturn(ret)
- self.assertTrue(os.path.isfile(os.path.join(name, "HEAD")))
- @with_tempdir()
- @slowTest
- def test_config_set_value_with_space_character(self, name):
- """
- git.config
- """
- self.run_function("git.init", [name])
- ret = self.run_state(
- "git.config_set",
- name="user.name",
- value="foo bar",
- repo=name,
- **{"global": False}
- )
- self.assertSaltTrueReturn(ret)
- @ensure_min_git
- @uses_git_opts
- class LocalRepoGitTest(ModuleCase, SaltReturnAssertsMixin):
- """
- Tests which do no require connectivity to github.com
- """
- def setUp(self):
- self.repo = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
- self.admin = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
- self.target = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
- for dirname in (self.repo, self.admin, self.target):
- self.addCleanup(shutil.rmtree, dirname, ignore_errors=True)
- # Create bare repo
- self.run_function("git.init", [self.repo], bare=True)
- # Clone bare repo
- self.run_function("git.clone", [self.admin], url=self.repo)
- self._commit(self.admin, "", message="initial commit")
- self._push(self.admin)
- def _commit(self, repo_path, content, message):
- with salt.utils.files.fopen(os.path.join(repo_path, "foo"), "a") as fp_:
- fp_.write(content)
- self.run_function("git.add", [repo_path, "."])
- self.run_function(
- "git.commit",
- [repo_path, message],
- git_opts='-c user.name="Foo Bar" -c user.email=foo@bar.com',
- )
- def _push(self, repo_path, remote="origin", ref="master"):
- self.run_function("git.push", [repo_path], remote=remote, ref=ref)
- def _test_latest_force_reset_setup(self):
- # Perform the initial clone
- ret = self.run_state("git.latest", name=self.repo, target=self.target)
- self.assertSaltTrueReturn(ret)
- # Make and push changes to remote repo
- self._commit(self.admin, content="Hello world!\n", message="added a line")
- self._push(self.admin)
- # Make local changes to clone, but don't commit them
- with salt.utils.files.fopen(os.path.join(self.target, "foo"), "a") as fp_:
- fp_.write("Local changes!\n")
- @slowTest
- def test_latest_force_reset_remote_changes(self):
- """
- This tests that an otherwise fast-forward change with local chanegs
- will not reset local changes when force_reset='remote_changes'
- """
- self._test_latest_force_reset_setup()
- # This should fail because of the local changes
- ret = self.run_state("git.latest", name=self.repo, target=self.target)
- self.assertSaltFalseReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("there are uncommitted changes", ret["comment"])
- self.assertIn("Set 'force_reset' to True (or 'remote-changes')", ret["comment"])
- self.assertEqual(ret["changes"], {})
- # Now run again with force_reset='remote_changes', the state should
- # succeed and discard the local changes
- ret = self.run_state(
- "git.latest",
- name=self.repo,
- target=self.target,
- force_reset="remote-changes",
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("Uncommitted changes were discarded", ret["comment"])
- self.assertIn("Repository was fast-forwarded", ret["comment"])
- self.assertNotIn("forced update", ret["changes"])
- self.assertIn("revision", ret["changes"])
- # Add new local changes, but don't commit them
- with salt.utils.files.fopen(os.path.join(self.target, "foo"), "a") as fp_:
- fp_.write("More local changes!\n")
- # Now run again with force_reset='remote_changes', the state should
- # succeed with an up-to-date message and mention that there are local
- # changes, telling the user how to discard them.
- ret = self.run_state(
- "git.latest",
- name=self.repo,
- target=self.target,
- force_reset="remote-changes",
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("up-to-date, but with uncommitted changes", ret["comment"])
- self.assertIn(
- "Set 'force_reset' to True to purge uncommitted changes", ret["comment"]
- )
- self.assertEqual(ret["changes"], {})
- @slowTest
- def test_latest_force_reset_true_fast_forward(self):
- """
- This tests that an otherwise fast-forward change with local chanegs
- does reset local changes when force_reset=True
- """
- self._test_latest_force_reset_setup()
- # Test that local changes are discarded and that we fast-forward
- ret = self.run_state(
- "git.latest", name=self.repo, target=self.target, force_reset=True
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("Uncommitted changes were discarded", ret["comment"])
- self.assertIn("Repository was fast-forwarded", ret["comment"])
- # Add new local changes
- with salt.utils.files.fopen(os.path.join(self.target, "foo"), "a") as fp_:
- fp_.write("More local changes!\n")
- # Running without setting force_reset should mention uncommitted changes
- ret = self.run_state("git.latest", name=self.repo, target=self.target)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("up-to-date, but with uncommitted changes", ret["comment"])
- self.assertIn(
- "Set 'force_reset' to True to purge uncommitted changes", ret["comment"]
- )
- self.assertEqual(ret["changes"], {})
- # Test that local changes are discarded
- ret = self.run_state(
- "git.latest", name=TEST_REPO, target=self.target, force_reset=True
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- assert "Uncommitted changes were discarded" in ret["comment"]
- assert "Repository was hard-reset" in ret["comment"]
- assert "forced update" in ret["changes"]
- @slowTest
- def test_latest_force_reset_true_non_fast_forward(self):
- """
- This tests that a non fast-forward change with divergent commits fails
- unless force_reset=True.
- """
- self._test_latest_force_reset_setup()
- # Reset to remote HEAD
- ret = self.run_state(
- "git.latest", name=self.repo, target=self.target, force_reset=True
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("Uncommitted changes were discarded", ret["comment"])
- self.assertIn("Repository was fast-forwarded", ret["comment"])
- # Make and push changes to remote repo
- self._commit(self.admin, content="New line\n", message="added another line")
- self._push(self.admin)
- # Make different changes to local file and commit locally
- self._commit(
- self.target,
- content="Different new line\n",
- message="added a different line",
- )
- # This should fail since the local clone has diverged and cannot
- # fast-forward to the remote rev
- ret = self.run_state("git.latest", name=self.repo, target=self.target)
- self.assertSaltFalseReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("this is not a fast-forward merge", ret["comment"])
- self.assertIn("Set 'force_reset' to True to force this update", ret["comment"])
- self.assertEqual(ret["changes"], {})
- # Repeat the state with force_reset=True and confirm that the hard
- # reset was performed
- ret = self.run_state(
- "git.latest", name=self.repo, target=self.target, force_reset=True
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertIn("Repository was hard-reset", ret["comment"])
- self.assertIn("forced update", ret["changes"])
- self.assertIn("revision", ret["changes"])
- @slowTest
- def test_renamed_default_branch(self):
- """
- Test the case where the remote branch has been removed
- https://github.com/saltstack/salt/issues/36242
- """
- # Rename remote 'master' branch to 'develop'
- os.rename(
- os.path.join(self.repo, "refs", "heads", "master"),
- os.path.join(self.repo, "refs", "heads", "develop"),
- )
- # Run git.latest state. This should successfully clone and fail with a
- # specific error in the comment field.
- ret = self.run_state(
- "git.latest", name=self.repo, target=self.target, rev="develop",
- )
- self.assertSaltFalseReturn(ret)
- self.assertEqual(
- ret[next(iter(ret))]["comment"],
- "Remote HEAD refers to a ref that does not exist. "
- "This can happen when the default branch on the "
- "remote repository is renamed or deleted. If you "
- "are unable to fix the remote repository, you can "
- "work around this by setting the 'branch' argument "
- "(which will ensure that the named branch is created "
- "if it does not already exist).\n\n"
- "Changes already made: {0} cloned to {1}".format(self.repo, self.target),
- )
- self.assertEqual(
- ret[next(iter(ret))]["changes"],
- {"new": "{0} => {1}".format(self.repo, self.target)},
- )
- # Run git.latest state again. This should fail again, with a different
- # error in the comment field, and should not change anything.
- ret = self.run_state(
- "git.latest", name=self.repo, target=self.target, rev="develop",
- )
- self.assertSaltFalseReturn(ret)
- self.assertEqual(
- ret[next(iter(ret))]["comment"],
- "Cannot set/unset upstream tracking branch, local "
- "HEAD refers to nonexistent branch. This may have "
- "been caused by cloning a remote repository for which "
- "the default branch was renamed or deleted. If you "
- "are unable to fix the remote repository, you can "
- "work around this by setting the 'branch' argument "
- "(which will ensure that the named branch is created "
- "if it does not already exist).",
- )
- self.assertEqual(ret[next(iter(ret))]["changes"], {})
- # Run git.latest state again with a branch manually set. This should
- # checkout a new branch and the state should pass.
- ret = self.run_state(
- "git.latest",
- name=self.repo,
- target=self.target,
- rev="develop",
- branch="develop",
- )
- # State should succeed
- self.assertSaltTrueReturn(ret)
- self.assertSaltCommentRegexpMatches(
- ret,
- "New branch 'develop' was checked out, with origin/develop "
- r"\([0-9a-f]{7}\) as a starting point",
- )
- # Only the revision should be in the changes dict.
- self.assertEqual(list(ret[next(iter(ret))]["changes"].keys()), ["revision"])
- # Since the remote repo was incorrectly set up, the local head should
- # not exist (therefore the old revision should be None).
- self.assertEqual(ret[next(iter(ret))]["changes"]["revision"]["old"], None)
- # Make sure the new revision is a SHA (40 chars, all hex)
- self.assertTrue(len(ret[next(iter(ret))]["changes"]["revision"]["new"]) == 40)
- self.assertTrue(
- all(
- [
- x in string.hexdigits
- for x in ret[next(iter(ret))]["changes"]["revision"]["new"]
- ]
- )
- )
|