-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[run-task] Add the ability to configure submodule checkouts. #512
base: main
Are you sure you want to change the base?
Changes from all commits
23ae857
b2c88d3
f66a825
89d7fe5
07caf67
a9c3849
1a79daa
018b4df
3612b26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,7 +126,41 @@ def test_install_pip_requirements( | |
{ | ||
"base-repo": "https://hg.mozilla.org/mozilla-unified", | ||
}, | ||
) | ||
), | ||
pytest.param( | ||
{ | ||
"REPOSITORY_TYPE": "git", | ||
"BASE_REPOSITORY": "https://github.com/mozilla-mobile/mozilla-vpn-client.git", | ||
"HEAD_REPOSITORY": "https://github.com/mozilla-mobile/mozilla-vpn-client.git", | ||
"HEAD_REV": "abcdef", | ||
"SUBMODULES": "3rdparty/i18n", | ||
}, | ||
{}, | ||
), | ||
pytest.param( | ||
{ | ||
"REPOSITORY_TYPE": "git", | ||
"BASE_REPOSITORY": "https://github.com/mozilla-mobile/mozilla-vpn-client.git", | ||
"HEAD_REPOSITORY": "https://github.com/mozilla-mobile/mozilla-vpn-client.git", | ||
"HEAD_REV": "abcdef", | ||
"SUBMODULES": "auto", | ||
}, | ||
{ | ||
"submodules": True, | ||
}, | ||
), | ||
pytest.param( | ||
{ | ||
"REPOSITORY_TYPE": "git", | ||
"BASE_REPOSITORY": "https://github.com/mozilla-mobile/mozilla-vpn-client.git", | ||
"HEAD_REPOSITORY": "https://github.com/mozilla-mobile/mozilla-vpn-client.git", | ||
"HEAD_REV": "abcdef", | ||
"SUBMODULES": "yes", | ||
}, | ||
{ | ||
"submodules": True, | ||
}, | ||
), | ||
], | ||
) | ||
def test_collect_vcs_options(monkeypatch, run_task_mod, env, extra_expected): | ||
|
@@ -159,6 +193,7 @@ def test_collect_vcs_options(monkeypatch, run_task_mod, env, extra_expected): | |
"ssh-secret-name": env.get("SSH_SECRET_NAME"), | ||
"sparse-profile": False, | ||
"store-path": env.get("HG_STORE_PATH"), | ||
"submodules": env.get("SUBMODULES"), | ||
} | ||
if "PIP_REQUIREMENTS" in env: | ||
expected["pip-requirements"] = os.path.join( | ||
|
@@ -283,54 +318,135 @@ def git_current_rev(cwd): | |
).strip() | ||
|
||
|
||
@pytest.fixture() | ||
def mock_homedir(monkeypatch): | ||
"Mock home directory to isolate git config changes" | ||
with tempfile.TemporaryDirectory() as fakehome: | ||
env = os.environ.copy() | ||
env["HOME"] = str(fakehome) | ||
monkeypatch.setattr(os, "environ", env) | ||
|
||
# Enable the git file protocol for testing. | ||
subprocess.check_call( | ||
["git", "config", "--global", "protocol.file.allow", "always"], env=env | ||
) | ||
yield str(fakehome) | ||
|
||
|
||
@pytest.fixture(scope="session") # Tests shouldn't change this repo | ||
def mock_git_repo(): | ||
"Mock repository with files, commits and branches for using as source" | ||
|
||
def _create_empty_repo(path): | ||
subprocess.check_call(["git", "init", "-b", "main", path]) | ||
subprocess.check_call(["git", "config", "user.name", "pytest"], cwd=path) | ||
subprocess.check_call(["git", "config", "user.email", "py@tes.t"], cwd=path) | ||
|
||
def _commit_file(message, filename, path): | ||
with open(os.path.join(path, filename), "w") as fout: | ||
fout.write("test file content") | ||
subprocess.check_call(["git", "add", filename], cwd=path) | ||
subprocess.check_call(["git", "commit", "-m", message], cwd=path) | ||
return git_current_rev(path) | ||
|
||
with tempfile.TemporaryDirectory() as repo: | ||
repo_path = str(repo) | ||
# Init git repo and setup user config | ||
subprocess.check_call(["git", "init", "-b", "main"], cwd=repo_path) | ||
subprocess.check_call(["git", "config", "user.name", "pytest"], cwd=repo_path) | ||
# Create the submodule repositories | ||
sm1_path = os.path.join(str(repo), "submodule1") | ||
_create_empty_repo(sm1_path) | ||
_commit_file("The first submodule", "first", sm1_path) | ||
|
||
sm2_path = os.path.join(str(repo), "submodule2") | ||
_create_empty_repo(sm2_path) | ||
_commit_file("The second submodule", "second", sm2_path) | ||
|
||
# Create the testing repository. | ||
repo_path = os.path.join(str(repo), "repository") | ||
_create_empty_repo(repo_path) | ||
|
||
# Add submodules (to main branch) | ||
subprocess.check_call( | ||
["git", "config", "user.email", "py@tes.t"], cwd=repo_path | ||
[ | ||
"git", | ||
"-c", | ||
"protocol.file.allow=always", | ||
"submodule", | ||
"add", | ||
f"file://{os.path.abspath(sm1_path)}", | ||
"sm1", | ||
], | ||
cwd=repo_path, | ||
) | ||
|
||
def _commit_file(message, filename): | ||
with open(os.path.join(repo, filename), "w") as fout: | ||
fout.write("test file content") | ||
subprocess.check_call(["git", "add", filename], cwd=repo_path) | ||
subprocess.check_call(["git", "commit", "-m", message], cwd=repo_path) | ||
return git_current_rev(repo_path) | ||
subprocess.check_call( | ||
[ | ||
"git", | ||
"-c", | ||
"protocol.file.allow=always", | ||
"submodule", | ||
"add", | ||
f"file://{os.path.abspath(sm2_path)}", | ||
"sm2", | ||
], | ||
cwd=repo_path, | ||
) | ||
subprocess.check_call(["git", "add", "sm1"], cwd=repo_path) | ||
subprocess.check_call(["git", "add", "sm2"], cwd=repo_path) | ||
subprocess.check_call(["git", "commit", "-m", "Add submodules"], cwd=repo_path) | ||
|
||
# Commit mainfile (to main branch) | ||
main_commit = _commit_file("Initial commit", "mainfile") | ||
main_commit = _commit_file("Initial commit", "mainfile", repo_path) | ||
|
||
# New branch mybranch | ||
subprocess.check_call(["git", "checkout", "-b", "mybranch"], cwd=repo_path) | ||
# Commit branchfile to mybranch branch | ||
branch_commit = _commit_file("File in mybranch", "branchfile") | ||
branch_commit = _commit_file("File in mybranch", "branchfile", repo_path) | ||
|
||
# Set current branch back to main | ||
subprocess.check_call(["git", "checkout", "main"], cwd=repo_path) | ||
yield {"path": repo_path, "main": main_commit, "branch": branch_commit} | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"base_ref,ref,files,hash_key", | ||
"base_ref,ref,submodules,files,hash_key", | ||
[ | ||
(None, None, ["mainfile"], "main"), | ||
(None, "main", ["mainfile"], "main"), | ||
(None, "mybranch", ["mainfile", "branchfile"], "branch"), | ||
("main", "main", ["mainfile"], "main"), | ||
("main", "mybranch", ["mainfile", "branchfile"], "branch"), | ||
# Check out the repository in a bunch of states. | ||
(None, None, None, ["mainfile"], "main"), | ||
(None, "main", None, ["mainfile"], "main"), | ||
(None, "mybranch", None, ["mainfile", "branchfile"], "branch"), | ||
("main", "main", None, ["mainfile"], "main"), | ||
("main", "mybranch", None, ["mainfile", "branchfile"], "branch"), | ||
# Same tests again - but with submodules. | ||
(None, None, True, ["mainfile", "sm1/first", "sm2/second"], "main"), | ||
(None, "main", True, ["mainfile", "sm1/first", "sm2/second"], "main"), | ||
( | ||
None, | ||
"mybranch", | ||
True, | ||
["mainfile", "branchfile", "sm1/first", "sm2/second"], | ||
"branch", | ||
), | ||
("main", "main", True, ["mainfile", "sm1/first", "sm2/second"], "main"), | ||
( | ||
"main", | ||
"mybranch", | ||
True, | ||
["mainfile", "branchfile", "sm1/first", "sm2/second"], | ||
"branch", | ||
), | ||
# Tests for submodule matching rules. | ||
(None, "main", "sm1", ["mainfile", "sm1/first"], "main"), | ||
(None, "main", "sm2", ["mainfile", "sm2/second"], "main"), | ||
(None, "main", "one:two:three", ["mainfile"], "main"), | ||
(None, "main", "one:two:sm1", ["mainfile", "sm1/first"], "main"), | ||
], | ||
) | ||
def test_git_checkout( | ||
mock_stdin, | ||
mock_homedir, | ||
run_task_mod, | ||
mock_git_repo, | ||
base_ref, | ||
ref, | ||
submodules, | ||
files, | ||
hash_key, | ||
): | ||
|
@@ -346,6 +462,7 @@ def test_git_checkout( | |
commit=None, | ||
ssh_key_file=None, | ||
ssh_known_hosts_file=None, | ||
submodules=submodules, | ||
) | ||
|
||
# Check desired files exist | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's an assert further down to check whether the outer repo is at the right revision. There's an assert checking for expected files makes sure something was checked out for the submodules, which I guess is good enough here, since the submodules themselves control which revs are used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is correct, a submodule in the superproject specifies the exact hash of child project to be checked out, and the |
||
|
@@ -367,6 +484,7 @@ def test_git_checkout( | |
|
||
def test_git_checkout_with_commit( | ||
mock_stdin, | ||
mock_homedir, | ||
run_task_mod, | ||
mock_git_repo, | ||
): | ||
|
@@ -382,6 +500,7 @@ def test_git_checkout_with_commit( | |
commit=mock_git_repo["branch"], | ||
ssh_key_file=None, | ||
ssh_known_hosts_file=None, | ||
submodules=None, | ||
) | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checkouts are often cached on individual workers. Will this work correctly if a checkout already contains an initialized submodule at the same (or different) revision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be technically possible to have different submodules checked out at different revisions, but technically a task should only rely on the submodules that it declares, and those would all get the
git submodule update
treatment below.That being said, I'm starting to think we should default to updating all submodules if
SUBMODULES
is not set. I don't think it's going to be easy to audit tasks to tell whether / which submodules they may or may not be using. So preserving backwards compatibility by defaulting to all of them will probably save us some headache.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will take a little bit of rethinking how what the env variables should mean to properly convey that (default disabled is easy because an unset environment variable is read as an empty string), but I will see what I can do.