Skip to content

Commit 4b3b615

Browse files
feat: renku clone command (#828)
* refactor: move githooks functionality to commands * feat: renku clone command * refactor: use Renku clone instead of Git clone
1 parent 4712872 commit 4b3b615

File tree

10 files changed

+335
-56
lines changed

10 files changed

+335
-56
lines changed

renku/cli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import click_completion
7070
import yaml
7171

72+
from renku.cli.clone import clone
7273
from renku.cli.config import config
7374
from renku.cli.dataset import dataset
7475
from renku.cli.doctor import doctor
@@ -187,6 +188,7 @@ def help(ctx):
187188

188189

189190
# Register subcommands:
191+
cli.add_command(clone)
190192
cli.add_command(config)
191193
cli.add_command(dataset)
192194
cli.add_command(doctor)

renku/cli/clone.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018-2019- Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Clone a Renku project.
19+
20+
Cloning a Renku project
21+
~~~~~~~~~~~~~~~~~~~~~~~
22+
23+
To clone a Renku project and set up required Git hooks and Git LFS use
24+
``renku clone`` command.
25+
26+
.. code-block:: console
27+
28+
$ renku clone git+ssh://host.io/namespace/project.git
29+
<destination-directory>
30+
31+
"""
32+
33+
import click
34+
35+
from renku.core.commands.clone import renku_clone
36+
from renku.core.commands.echo import GitProgress
37+
38+
39+
@click.command()
40+
@click.option(
41+
'--pull-data', is_flag=True, help='Pull data from Git-LFS.', default=False
42+
)
43+
@click.argument('url')
44+
@click.argument('path', required=False, default=None)
45+
def clone(pull_data, url, path):
46+
"""Clone a Renku repository."""
47+
click.echo('Cloning {} ...'.format(url))
48+
49+
skip_smudge = not pull_data
50+
renku_clone(
51+
url=url, path=path, skip_smudge=skip_smudge, progress=GitProgress()
52+
)
53+
click.secho('OK', fg='green')

renku/cli/githooks.py

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,9 @@
3838
3939
"""
4040

41-
import stat
42-
from pathlib import Path
43-
4441
import click
4542

46-
from renku.core.commands.client import pass_local_client
47-
48-
HOOKS = ('pre-commit', )
43+
from renku.core.commands.githooks import install_githooks, uninstall_githooks
4944

5045

5146
@click.group()
@@ -55,42 +50,14 @@ def githooks():
5550

5651
@githooks.command()
5752
@click.option('--force', is_flag=True, help='Override existing hooks.')
58-
@pass_local_client
59-
def install(client, force):
53+
def install(force):
6054
"""Install Git hooks."""
61-
import pkg_resources
62-
from git.index.fun import hook_path as get_hook_path
63-
64-
for hook in HOOKS:
65-
hook_path = Path(get_hook_path(hook, client.repo.git_dir))
66-
if hook_path.exists():
67-
if not force:
68-
click.echo(
69-
'Hook already exists. Skipping {0}'.format(str(hook_path)),
70-
err=True
71-
)
72-
continue
73-
else:
74-
hook_path.unlink()
75-
76-
# Make sure the hooks directory exists.
77-
hook_path.parent.mkdir(parents=True, exist_ok=True)
78-
79-
Path(hook_path).write_bytes(
80-
pkg_resources.resource_string(
81-
'renku.data', '{hook}.sh'.format(hook=hook)
82-
)
83-
)
84-
hook_path.chmod(hook_path.stat().st_mode | stat.S_IEXEC)
55+
install_githooks(force)
56+
click.secho('OK', fg='green')
8557

8658

8759
@githooks.command()
88-
@pass_local_client
89-
def uninstall(client):
60+
def uninstall():
9061
"""Uninstall Git hooks."""
91-
from git.index.fun import hook_path as get_hook_path
92-
93-
for hook in HOOKS:
94-
hook_path = Path(get_hook_path(hook, client.repo.git_dir))
95-
if hook_path.exists():
96-
hook_path.unlink()
62+
uninstall_githooks()
63+
click.secho('OK', fg='green')

renku/core/commands/clone.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018-2019- Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Clone a Renku repo along with all Renku-specific initializations."""
19+
20+
from renku.core.management.clone import clone
21+
22+
from .client import pass_local_client
23+
24+
25+
@pass_local_client
26+
def renku_clone(
27+
client,
28+
url,
29+
path=None,
30+
install_githooks=True,
31+
skip_smudge=True,
32+
progress=None
33+
):
34+
"""Clone Renku project repo, install Git hooks and LFS."""
35+
install_lfs = client.use_external_storage
36+
clone(
37+
url=url,
38+
path=path,
39+
install_githooks=install_githooks,
40+
install_lfs=install_lfs,
41+
skip_smudge=skip_smudge,
42+
progress=progress
43+
)

renku/core/commands/echo.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import os
2222

2323
import click
24+
from git.remote import RemoteProgress
2425

2526
WARNING = click.style('Warning: ', bold=True, fg='yellow')
2627

@@ -45,3 +46,23 @@ def echo_via_pager(*args, **kwargs):
4546
show_pos=True,
4647
item_show_func=lambda x: x,
4748
)
49+
50+
51+
class GitProgress(RemoteProgress):
52+
"""Progress printing for GitPython."""
53+
54+
def __init__(self):
55+
"""Initialize a Git progress printer."""
56+
super().__init__()
57+
self._previous_line_length = 0
58+
59+
def update(self, op_code, cur_count, max_count=None, message=''):
60+
"""Callback for printing Git operation status."""
61+
self._clear_line()
62+
print(self._cur_line, end='\r')
63+
self._previous_line_length = len(self._cur_line)
64+
if (op_code & RemoteProgress.END) != 0:
65+
print()
66+
67+
def _clear_line(self):
68+
print(self._previous_line_length * ' ', end='\r')

renku/core/commands/githooks.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018-2019- Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Install and uninstall Git hooks."""
19+
20+
import click
21+
22+
from renku.core.management.githooks import install, uninstall
23+
24+
from .client import pass_local_client
25+
from .echo import WARNING
26+
27+
28+
@pass_local_client
29+
def install_githooks(client, force):
30+
"""Install Git hooks."""
31+
warning_messages = install(client=client, force=force)
32+
if warning_messages:
33+
for message in warning_messages:
34+
click.echo(WARNING + message)
35+
36+
37+
@pass_local_client
38+
def uninstall_githooks(client):
39+
"""Uninstall Git hooks."""
40+
uninstall(client=client)

renku/core/management/clone.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018-2019- Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Clone a Renku repo along with all Renku-specific initializations."""
19+
20+
import os
21+
22+
from git import GitCommandError, Repo
23+
24+
from renku.core import errors
25+
from renku.core.management.githooks import install
26+
27+
28+
def clone(
29+
url,
30+
path=None,
31+
install_githooks=True,
32+
install_lfs=True,
33+
skip_smudge=True,
34+
recursive=True,
35+
depth=None,
36+
progress=None
37+
):
38+
"""Clone Renku project repo, install Git hooks and LFS."""
39+
from renku.core.management.client import LocalClient
40+
41+
path = path or '.'
42+
# Clone the project
43+
if skip_smudge:
44+
os.environ['GIT_LFS_SKIP_SMUDGE'] = '1'
45+
try:
46+
repo = Repo.clone_from(
47+
url, path, recursive=recursive, depth=depth, progress=progress
48+
)
49+
except GitCommandError as e:
50+
raise errors.GitError(
51+
'Cannot clone remote Renku project: {}'.format(url)
52+
) from e
53+
54+
client = LocalClient(path)
55+
56+
if install_githooks:
57+
install(client=client, force=True)
58+
59+
if install_lfs:
60+
command = ['git', 'lfs', 'install', '--local', '--force']
61+
if skip_smudge:
62+
command += ['--skip-smudge']
63+
try:
64+
repo.git.execute(command=command, with_exceptions=True)
65+
except GitCommandError as e:
66+
raise errors.GitError('Cannot install Git LFS') from e
67+
68+
return repo

renku/core/management/datasets.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from git import GitCommandError, GitError, Repo
3535

3636
from renku.core import errors
37+
from renku.core.management.clone import clone
3738
from renku.core.management.config import RENKU_HOME
3839
from renku.core.models.datasets import Dataset, DatasetFile, DatasetTag
3940
from renku.core.models.git import GitURL
@@ -755,9 +756,13 @@ def checkout(repo, ref):
755756
repo = Repo(str(repo_path))
756757
if repo.remotes.origin.url == url:
757758
try:
758-
repo.git.fetch()
759+
repo.git.fetch(all=True)
759760
repo.git.checkout(ref)
760-
repo.git.pull()
761+
try:
762+
repo.git.pull()
763+
except GitError:
764+
# When ref is not a branch, an error is thrown
765+
pass
761766
except GitError:
762767
# ignore the error and try re-cloning
763768
pass
@@ -772,25 +777,17 @@ def checkout(repo, ref):
772777
format(repo_path)
773778
)
774779

780+
repo = clone(url, path=str(repo_path), install_githooks=False)
781+
782+
# Because the name of the default branch is not always 'master', we
783+
# create an alias of the default branch when cloning the repo. It
784+
# is used to refer to the default branch later.
785+
renku_ref = 'refs/heads/' + RENKU_BRANCH
775786
try:
776-
os.environ['GIT_LFS_SKIP_SMUDGE'] = '1'
777-
repo = Repo.clone_from(url, str(repo_path), recursive=True)
778-
# Because the name of the default branch is not always 'master', we
779-
# create an alias of the default branch when cloning the repo. It
780-
# is used to refer to the default branch later.
781-
renku_ref = 'refs/heads/' + RENKU_BRANCH
782787
repo.git.execute([
783788
'git', 'symbolic-ref', renku_ref, repo.head.reference.path
784789
])
785790
checkout(repo, ref)
786-
# Disable Git LFS smudge filter
787-
repo.git.execute(
788-
command=[
789-
'git', 'lfs', 'install', '--local', '--skip-smudge',
790-
'--force'
791-
],
792-
with_exceptions=False
793-
)
794791
except GitCommandError as e:
795792
raise errors.GitError(
796793
'Cannot clone remote Git repo: {}'.format(url)

0 commit comments

Comments
 (0)