Skip to content

Commit 7477bd8

Browse files
ensure branch in autoconfigure Django script. by Piotr and Filip
1 parent a430bb9 commit 7477bd8

File tree

6 files changed

+126
-13
lines changed

6 files changed

+126
-13
lines changed

pythonanywhere/django_project.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,35 @@
1111

1212

1313
class DjangoProject(Project):
14-
1514
def download_repo(self, repo, nuke):
1615
if nuke and self.project_path.exists():
1716
shutil.rmtree(str(self.project_path))
1817
subprocess.check_call(['git', 'clone', repo, str(self.project_path)])
1918

19+
def ensure_branch(self, branch):
20+
output = subprocess.check_output(
21+
["git", "-C", str(self.project_path), "branch", "-r"]
22+
).decode().rstrip().split("\n")
23+
branches = [x.strip().replace("origin/", "") for x in output if "->" not in x]
24+
if branch == "None" and len(branches) == 1:
25+
return
26+
if branch == "None":
27+
shutil.rmtree(str(self.project_path))
28+
raise SanityException(
29+
"There are many branches in your repo. "
30+
"You need to specify which branch to use by adding "
31+
"--branch=<branch> option to the command."
32+
)
33+
if branch not in branches:
34+
shutil.rmtree(str(self.project_path))
35+
raise SanityException(f"You do not have a {branch} branch in your repo")
36+
#
37+
current_branch = subprocess.check_output(
38+
["git", "-C", str(self.project_path), "rev-parse", "--abbrev-ref HEAD"]
39+
).decode().strip()
40+
41+
if current_branch != branch:
42+
subprocess.check_call(["git", "-C", str(self.project_path), "checkout", branch])
2043

2144
def create_virtualenv(self, django_version=None, nuke=False):
2245
self.virtualenv.create(nuke=nuke)

pythonanywhere/django_project.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from typing import Optional
55

66
class DjangoProject(Project):
77
def download_repo(self, repo: str, nuke: bool) -> None: ...
8+
def ensure_branch(self, branch: str) -> None: ...
89
def create_virtualenv(self, django_version: Optional[str] = ..., nuke: bool = ...) -> None: ...
910
def detect_requirements(self): ...
1011
def run_startproject(self, nuke: bool) -> None: ...

scripts/pa_autoconfigure_django.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
- adds static files config
99
1010
Usage:
11-
pa_autoconfigure_django.py <git-repo-url> [--domain=<domain> --python=<python-version>] [--nuke]
11+
pa_autoconfigure_django.py <git-repo-url> [--branch=<branch> --domain=<domain> --python=<python-version>] [--nuke]
1212
1313
Options:
14+
--branch=<branch> Branch name in case of multiple branches [default: None]
1415
--domain=<domain> Domain name, eg www.mydomain.com [default: your-username.pythonanywhere.com]
1516
--python=<python-version> Python version, eg "3.8" [default: 3.6]
1617
--nuke *Irrevocably* delete any existing web app config on this domain. Irrevocably.
@@ -23,11 +24,12 @@
2324
from pythonanywhere.utils import ensure_domain
2425

2526

26-
def main(repo_url, domain, python_version, nuke):
27+
def main(repo_url, branch, domain, python_version, nuke):
2728
domain = ensure_domain(domain)
2829
project = DjangoProject(domain, python_version)
2930
project.sanity_checks(nuke=nuke)
3031
project.download_repo(repo_url, nuke=nuke),
32+
project.ensure_branch(branch),
3133
project.create_virtualenv(nuke=nuke)
3234
project.create_webapp(nuke=nuke)
3335
project.add_static_file_mappings()
@@ -44,4 +46,10 @@ def main(repo_url, domain, python_version, nuke):
4446

4547
if __name__ == '__main__':
4648
arguments = docopt(__doc__)
47-
main(arguments['<git-repo-url>'], arguments['--domain'], arguments['--python'], nuke=arguments.get('--nuke'))
49+
main(
50+
arguments['<git-repo-url>'],
51+
arguments['--branch'],
52+
arguments['--domain'],
53+
arguments['--python'],
54+
nuke=arguments.get('--nuke')
55+
)

tests/test_cli_schedule.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ def mock_confirm(mocker):
3939
return mocker.patch("cli.schedule.typer.confirm")
4040

4141

42-
@pytest.mark.clischeduleset
4342
class TestSet:
4443
def test_calls_all_stuff_in_right_order(self, mocker):
4544
mock_logger = mocker.patch("cli.schedule.get_logger")
@@ -93,7 +92,6 @@ def test_logs_warning_when_create_schedule_raises(self, mocker):
9392
assert mock_logger.warning.call_args == call(mock_snakesay.return_value)
9493

9594

96-
@pytest.mark.clischeduledeleteall
9795
class TestDeleteAllTasks:
9896
def test_deletes_all_tasks_with_user_permission(self, task_list, mock_confirm):
9997
mock_confirm.return_value = True
@@ -130,7 +128,6 @@ def test_sets_logging_to_info(self, mocker):
130128
assert mock_logger.call_args == call(set_info=True)
131129

132130

133-
@pytest.mark.clischeduledelete
134131
class TestDeleteTaskById:
135132
def test_deletes_one_task(self, mocker):
136133
mock_task_from_id = mocker.patch("cli.schedule.get_task_from_id")
@@ -184,7 +181,6 @@ def task_from_id(mocker):
184181
yield task
185182

186183

187-
@pytest.mark.clischeduleget
188184
class TestGet:
189185
def test_logs_all_task_specs_using_tabulate(self, mocker, task_from_id):
190186
mock_tabulate = mocker.patch("cli.schedule.tabulate")
@@ -256,7 +252,6 @@ def test_complains_when_no_id_provided(self):
256252
assert "Missing argument 'id'" in result.stdout
257253

258254

259-
@pytest.mark.clischedulelist
260255
class TestList:
261256
def test_logs_table_with_correct_headers_and_values(self, mocker, task_list):
262257
mock_logger = mocker.patch("cli.schedule.get_logger")
@@ -300,7 +295,6 @@ def test_warns_when_wrong_format_provided(self, mocker, task_list):
300295
assert "Table format has to be one of" in result.stdout
301296

302297

303-
@pytest.mark.clischeduleupdate
304298
class TestUpdate:
305299
def test_enables_task_and_sets_porcelain(self, mocker):
306300
mock_task_from_id = mocker.patch("cli.schedule.get_task_from_id")

tests/test_django_project.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_nuke_option_deletes_directory_first(self, mock_subprocess, fake_home, v
3636
mock_subprocess.check_call.side_effect = lambda *_, **__: Path(project.project_path).mkdir()
3737

3838
project.download_repo("repo", nuke=True)
39-
assert "old-thing.txt" not in project.project_path.iterdir()
39+
assert (project.project_path / "old-thing.txt") not in project.project_path.iterdir()
4040

4141
def test_nuke_option_ignores_directory_doesnt_exist(self, mock_subprocess, fake_home, virtualenvs_folder):
4242
project = DjangoProject("www.a.domain.com", "py.version")
@@ -47,6 +47,92 @@ def test_nuke_option_ignores_directory_doesnt_exist(self, mock_subprocess, fake_
4747
assert project.project_path.is_dir()
4848

4949

50+
class TestEnsureBranch:
51+
def test_checks_happy_path_with_one_branch(self, mock_subprocess, fake_home, virtualenvs_folder):
52+
project = DjangoProject("www.a.domain.com", "py.version")
53+
mock_subprocess.check_output.side_effect = [
54+
b" origin/HEAD -> origin/bar\n origin/bar\n", b"bar\n"
55+
]
56+
project.ensure_branch("None") # should not raise
57+
58+
def test_checks_if_branch_exists(self, mock_subprocess, fake_home, virtualenvs_folder):
59+
project = DjangoProject("www.a.domain.com", "py.version")
60+
mock_subprocess.check_output.side_effect = [
61+
b" origin/HEAD -> origin/bar\n origin/foo\n origin/bar\n origin/baz\n", b"bar\n"
62+
]
63+
project.ensure_branch("foo") # should not raise
64+
65+
def test_checks_if_branch_exists(self, mock_subprocess, fake_home, virtualenvs_folder):
66+
project = DjangoProject("www.a.domain.com", "py.version")
67+
mock_subprocess.check_output.side_effect = [
68+
b" origin/HEAD -> origin/bar\n origin/foo\n origin/bar\n origin/baz\n", b"bar\n"
69+
]
70+
71+
project.ensure_branch("foo") # should not raise
72+
73+
def test_deletes_directory_if_branch_does_not_exist(self, mock_subprocess, fake_home, virtualenvs_folder):
74+
project = DjangoProject("www.a.domain.com", "py.version")
75+
project.project_path.mkdir()
76+
mock_subprocess.check_output.return_value = b" origin/HEAD -> origin/bar\n origin/bar\n origin/baz\n"
77+
78+
with pytest.raises(SanityException):
79+
project.ensure_branch("foo")
80+
81+
assert not project.project_path.exists()
82+
83+
def test_program_raises_if_branch_does_not_exist(self, mock_subprocess, fake_home, virtualenvs_folder):
84+
project = DjangoProject("www.a.domain.com", "py.version")
85+
project.project_path.mkdir()
86+
mock_subprocess.check_output.return_value = b" origin/HEAD -> origin/bar\n origin/bar\n origin/baz\n"
87+
88+
with pytest.raises(SanityException) as e:
89+
project.ensure_branch("foo")
90+
91+
assert "You do not have a foo branch in your repo" in str(e.value)
92+
93+
def test_deletes_directory_if_multiple_branches(self, mock_subprocess, fake_home, virtualenvs_folder):
94+
project = DjangoProject("www.a.domain.com", "py.version")
95+
project.project_path.mkdir()
96+
mock_subprocess.check_output.return_value = b" origin/HEAD -> origin/bar\n origin/foo\n origin/bar\n origin/baz\n"
97+
98+
with pytest.raises(SanityException) as e:
99+
project.ensure_branch("None")
100+
101+
assert not project.project_path.exists()
102+
103+
def test_raises_if_multiple_branches(self, mock_subprocess, fake_home, virtualenvs_folder):
104+
project = DjangoProject("www.a.domain.com", "py.version")
105+
project.project_path.mkdir()
106+
mock_subprocess.check_output.return_value = b" origin/HEAD -> origin/bar\n origin/foo\n origin/bar\n origin/baz\n"
107+
108+
with pytest.raises(SanityException) as e:
109+
project.ensure_branch("None")
110+
111+
assert "There are many branches in your repo." in str(e.value)
112+
113+
def test_checkouts_branch_if_not_current_head(self, mock_subprocess, fake_home, virtualenvs_folder):
114+
project = DjangoProject("www.a.domain.com", "py.version")
115+
mock_subprocess.check_output.side_effect = [
116+
b" origin/HEAD -> origin/bar\n origin/foo\n origin/bar\n origin/baz\n", b"bar\n"
117+
]
118+
119+
project.ensure_branch("baz")
120+
121+
assert mock_subprocess.check_call.call_args == call(
122+
["git", "-C", str(project.project_path), "checkout", "baz"]
123+
)
124+
125+
def test_does_not_checkouts_branch_if_current_head(self, mock_subprocess, fake_home, virtualenvs_folder):
126+
project = DjangoProject("www.a.domain.com", "py.version")
127+
mock_subprocess.check_output.side_effect = [
128+
b" origin/HEAD -> origin/bar\n origin/foo\n origin/bar\n origin/baz\n", b"bar\n"
129+
]
130+
131+
project.ensure_branch("bar")
132+
133+
assert mock_subprocess.check_call.call_count == 0
134+
135+
50136
class TestDetectDjangoVersion:
51137
def test_is_versionless_django_by_default(self, fake_home, virtualenvs_folder):
52138
project = DjangoProject("mydomain.com", "python.version")

tests/test_pa_autoconfigure_django.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ class TestMain:
1313

1414
def test_calls_all_stuff_in_right_order(self):
1515
with patch('scripts.pa_autoconfigure_django.DjangoProject') as mock_DjangoProject:
16-
main('repo.url', 'www.domain.com', 'python.version', nuke='nuke option')
16+
main('repo.url', 'foo', 'www.domain.com', 'python.version', nuke='nuke option')
1717
assert mock_DjangoProject.call_args == call('www.domain.com', 'python.version')
1818
assert mock_DjangoProject.return_value.method_calls == [
1919
call.sanity_checks(nuke='nuke option'),
2020
call.download_repo('repo.url', nuke='nuke option'),
21+
call.ensure_branch("foo"),
2122
call.create_virtualenv(nuke='nuke option'),
2223
call.create_webapp(nuke='nuke option'),
2324
call.add_static_file_mappings(),
@@ -40,7 +41,7 @@ def test_actually_works_against_example_repo(
4041
with patch('scripts.pa_autoconfigure_django.DjangoProject.update_wsgi_file'):
4142
with patch('scripts.pa_autoconfigure_django.DjangoProject.start_bash'):
4243
with patch('pythonanywhere.api.webapp.call_api'):
43-
main(repo, domain, running_python_version, nuke=False)
44+
main(repo, "None", domain, running_python_version, nuke=False)
4445

4546
expected_django_version = '3.0.6'
4647
expected_virtualenv = virtualenvs_folder / domain

0 commit comments

Comments
 (0)