Newer
Older
from urllib.request import urlretrieve
from cadetrdm.io_utils import recursive_chmod, write_lines_to_file, wait_for_user, init_lfs
from cadetrdm.remote_integration import GitHubRemote, GitLabRemote
try:
import git
except ImportError:
# Adding this hint to save users the confusion of trying $pip install git
raise ImportError("No module named git, please install the gitpython package")
from cadetrdm.web_utils import ssh_url_to_http_url
from cadetrdm.io_utils import delete_path
def validate_is_output_repo(path_to_repo):
with open(os.path.join(path_to_repo, ".cadet-rdm-data.json"), "r") as file_handle:
rdm_data = json.load(file_handle)
if rdm_data["is_project_repo"]:
raise ValueError("Please use the URL to the output repository.")
def __init__(self, repository_path=None, search_parent_directories=True, *args, **kwargs):
Base class handling most git workflows.
:param repository_path:
Path to the root directory of the repository.
:param search_parent_directories:
if True, all parent directories will be searched for a valid repo as well.
Please note that this was the default behaviour in older versions of GitPython,
which is considered a bug though.
:param args:
Args handed to git.Repo()
:param kwargs:
Kwargs handed to git.Repo()
if repository_path is None or repository_path == ".":
if type(repository_path) is str:
repository_path = Path(repository_path)
self._git_repo = git.Repo(repository_path, search_parent_directories=search_parent_directories, *args, **kwargs)
self._git = self._git_repo.git
self._most_recent_branch = self.active_branch.name
self._earliest_commit = None
@property
def active_branch(self):
@property
def untracked_files(self):
@property
def current_commit_hash(self):
return str(self.head.commit)
@property
def path(self):
return Path(self._git_repo.working_dir)
@property
def bare(self):
return self._git_repo.bare
@property
def working_dir(self):
print("Deprecation Warning. .working_dir is getting replaced with .path")
@property
def head(self):
@property
def remotes(self):
@property
def remote_urls(self):
if len(self.remotes) == 0:
print(RuntimeWarning(f"No remote for repo at {self.path} set yet. Please add remote ASAP."))
return [str(remote.url) for remote in self.remotes]
def earliest_commit(self):
if self._earliest_commit is None:
*_, earliest_commit = self._git_repo.iter_commits()
self._earliest_commit = earliest_commit
return self._earliest_commit
return list()
@property
def data_json_path(self):
@property
def cache_json_path(self):
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
return self.path / ".cadet-rdm-cache.json"
@property
def has_changes_upstream(self):
try:
remote_hash = str(self.remotes[0].fetch()[0].commit)
if self.current_commit_hash != remote_hash:
return True
else:
return False
except git.GitCommandError as e:
traceback.print_exc()
print(f"Git command error in {self.path}: {e}")
def fetch(self):
self._git.fetch()
def update(self):
try:
self.fetch()
if self.has_changes_upstream:
print(f"New changes detected in {self.remotes[0].origin}, pulling updates...")
self.remotes[0].origin.pull()
except git.GitCommandError as e:
traceback.print_exc()
print(f"Git command error in {self.path}: {e}")
:param remote_url:
:param remote_name:
:return:
"""
self._git_repo.create_remote(remote_name, url=remote_url)
with open(self.data_json_path, "r") as handle:
rdm_data = json.load(handle)
if rdm_data["is_project_repo"]:
# This folder is a project repo. Use a project repo class to easily access the output repo.
output_repo = ProjectRepo(self.path).output_repo
if output_repo.active_branch != "main":
if output_repo.exist_uncomitted_changes:
output_repo.stash_all_changes()
output_repo.checkout("main")
output_repo.add_list_of_remotes_in_readme_file("project_repo", self.remote_urls)
output_repo.commit("Add remote for project repo", verbosity=0)
if rdm_data["is_output_repo"]:
project_repo.update_output_remotes_json()
project_repo.add_list_of_remotes_in_readme_file("output_repo", self.remote_urls)
project_repo.commit("Add remote for output repo", verbosity=0)
def add_filetype_to_lfs(self, file_type):
"""
Add the filetype given in file_type to the GIT-LFS tracking
:param file_type:
Wildcard formatted string. Examples: "*.png" or "*.xlsx"
:return:
"""
init_lfs(lfs_filetypes=[file_type], path=self.path)
self.add_all_files()
self.commit(f"Add {file_type} to lfs")
def import_remote_repo(self, source_repo_location, source_repo_branch, target_repo_location=None):
"""
Import a remote repo and update the cadet-rdm-cache
:param source_repo_location:
Path or URL to the source repo.
Example https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/agile_cadet_rdm_presentation_output.git
or git@jugit.fz-juelich.de:IBG-1/ModSim/cadet/agile_cadet_rdm_presentation_output.git
:param source_repo_branch:
Branch of the source repo to check out.
:param target_repo_location:
Place to store the repo. If None, the external_cache folder is used.
:return:
Path to the cloned repository
"""
if "://" in str(source_repo_location):
source_repo_name = source_repo_location.split("/")[-1]
else:
source_repo_name = Path(source_repo_location).name
target_repo_location = self.path / "external_cache" / source_repo_name
target_repo_location = self.path / target_repo_location
self.add_path_to_gitignore(target_repo_location)
print(f"Cloning from {source_repo_location} into {target_repo_location}")
multi_options = ["--filter=blob:none", "--branch", source_repo_branch, "--single-branch"]
repo = git.Repo.clone_from(source_repo_location, target_repo_location, multi_options=multi_options)
repo.git.clear_cache()
self.update_cadet_rdm_cache_json(source_repo_branch=source_repo_branch,
target_repo_location=target_repo_location,
source_repo_location=source_repo_location)
return target_repo_location
def add_path_to_gitignore(self, path_to_be_ignored):
"""
Add the path to the .gitignore file
:param path_to_be_ignored:
:return:
"""
path_to_be_ignored = self.ensure_relative_path(path_to_be_ignored)
with open(self.path / ".gitignore", "r") as file_handle:
gitignore = file_handle.readlines()
gitignore[-1] += "\n" # Sometimes there is no trailing newline
if str(path_to_be_ignored) + "\n" not in gitignore:
gitignore.append(str(path_to_be_ignored) + "\n")
with open(self.path / ".gitignore", "w") as file_handle:
file_handle.writelines(gitignore)
def update_cadet_rdm_cache_json(self, source_repo_location, source_repo_branch, target_repo_location):
"""
Update the information in the .cadet_rdm_cache.json file
:param source_repo_location:
Path or URL to the source repo.
:param source_repo_branch:
Name of the branch to check out.
:param target_repo_location:
Path where to put the repo or data
"""
with open(self.cache_json_path, "w") as file_handle:
file_handle.writelines("{}")
with open(self.cache_json_path, "r") as file_handle:
rdm_cache = json.load(file_handle)
repo = BaseRepo(target_repo_location)
commit_hash = repo.current_commit_hash
if "__example/path/to/repo__" in rdm_cache.keys():
rdm_cache.pop("__example/path/to/repo__")
target_repo_location = str(self.ensure_relative_path(target_repo_location))
if isinstance(source_repo_location, Path):
source_repo_location = source_repo_location.as_posix()
rdm_cache[target_repo_location] = {
"source_repo_location": source_repo_location,
"branch_name": source_repo_branch,
"commit_hash": commit_hash,
}
with open(self.cache_json_path, "w") as file_handle:
json.dump(rdm_cache, file_handle, indent=2)
def ensure_relative_path(self, input_path):
"""
Turn the input path into a relative path, relative to the repo working directory.
:param input_path:
:return:
"""
if type(input_path) is str:
input_path = Path(input_path)
if input_path.is_absolute:
relative_path = input_path.relative_to(self.path)
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
else:
relative_path = input_path
return relative_path
def verify_unchanged_cache(self):
"""
Verify that all repos referenced in .cadet-rdm-data.json are
in an unmodified state. Raises a RuntimeError if the commit hash has changed or if
uncommited changes are found.
:return:
"""
with open(self.cache_json_path, "r") as file_handle:
rdm_cache = json.load(file_handle)
if "__example/path/to/repo__" in rdm_cache.keys():
rdm_cache.pop("__example/path/to/repo__")
for repo_location, repo_info in rdm_cache.items():
try:
repo = BaseRepo(repo_location)
repo._git.clear_cache()
except git.exc.NoSuchPathError:
raise git.exc.NoSuchPathError(f"The imported repository at {repo_location} was not found.")
self.verify_cache_folder_is_unchanged(repo_location, repo_info["commit_hash"])
def verify_cache_folder_is_unchanged(self, repo_location, commit_hash):
"""
Verify that the repo located at repo_location has no uncommited changes and that the current commit_hash
is equal to the given commit_hash
:param repo_location:
:param commit_hash:
:return:
"""
repo = BaseRepo(repo_location)
commit_changed = repo.current_commit_hash != commit_hash
uncommited_changes = repo.exist_uncomitted_changes
if commit_changed or uncommited_changes:
raise RuntimeError(f"The contents of {repo_location} have been modified. Don't do that.")
repo._git.clear_cache()
def checkout(self, *args, **kwargs):
self._most_recent_branch = self.active_branch
self._git.checkout(*args, **kwargs)
def push(self, remote=None, local_branch=None, remote_branch=None, push_all=True):
Name of the remote branch to push to.
Loading
Loading full blame...