-- -- --- --- --- --- --- --- ------- ------- ------- |
"""
-- -- --- --- --- --- --- --- ------- ------- ------- |
Git Archiver.
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
Implementation of the archiver API for the gitpython module.
-- -- --- --- --- --- --- --- ------- ------- ------- |
"""
-- -- --- --- --- --- --- --- ------- ------- ------- |
import logging
-- -- --- --- --- --- --- --- ------- ------- ------- |
from typing import Any, Dict, List, Tuple
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
import git.exc
-- -- --- --- --- --- --- --- ------- ------- ------- |
from git import Commit
-- -- --- --- --- --- --- --- ------- ------- ------- |
from git.repo import Repo
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.archivers import BaseArchiver, Revision
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.config.types import WilyConfig
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
logger = logging.getLogger(__name__)
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
01 -- --- --- --- --- --- --- ------- ------- ------- |
class InvalidGitRepositoryError(Exception):
01 -- --- --- --- --- --- --- ------- ------- ------- |
"""Error for when a folder is not a git repo."""
01 -- --- --- --- --- --- --- ------- ------- ------- |
01 -- --- --- --- --- --- --- ------- ------- ------- |
pass
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
02 -- --- --- --- --- --- --- ------- ------- ------- |
class DirtyGitRepositoryError(Exception):
02 -- --- --- --- --- --- --- ------- ------- ------- |
"""Error for a dirty git repository (untracked files)."""
02 -- --- --- --- --- --- --- ------- ------- ------- |
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
def __init__(self, untracked_files: List[str]):
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
"""
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
Raise error for untracked files.
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
:param untracked_files: List of untracked files
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
:param untracked_files: ``list``
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
"""
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
self.untracked_files = untracked_files
02 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
self.message = "Dirty repository, make sure you commit/stash files first"
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
def get_tracked_files_dirs(repo: Repo, commit: Commit) -> Tuple[List[str], List[str]]:
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
"""Get tracked files in a repo for a commit hash using ls-tree."""
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
paths = repo.git.execute(
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
["git", "ls-tree", "--name-only", "--full-tree", "-r", commit.hexsha],
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
with_extended_output=False,
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
as_process=False,
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
stdout_as_string=True,
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
).split("\n")
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
dirs = [""] + repo.git.execute(
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
["git", "ls-tree", "--name-only", "--full-tree", "-r", "-d", commit.hexsha],
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
with_extended_output=False,
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
as_process=False,
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
stdout_as_string=True,
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
).split("\n")
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
return paths, dirs
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
def whatchanged(
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
commit_a: Commit, commit_b: Commit
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
) -> Tuple[List[str], List[str], List[str]]:
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
"""Get files added, modified and deleted between commits."""
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
diffs = commit_b.diff(commit_a)
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
added_files = []
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
modified_files = []
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
deleted_files = []
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
for diff in diffs:
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
if diff.new_file:
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
added_files.append(diff.b_path)
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
elif diff.deleted_file:
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
deleted_files.append(diff.a_path)
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
elif diff.renamed_file:
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
added_files.append(diff.b_path)
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
deleted_files.append(diff.a_path)
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
elif diff.change_type == "M":
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
modified_files.append(diff.a_path)
-- 06 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
return added_files, modified_files, deleted_files
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
03 -- --- --- --- --- --- --- ------- ------- ------- |
class GitArchiver(BaseArchiver):
03 -- --- --- --- --- --- --- ------- ------- ------- |
"""Gitpython implementation of the base archiver."""
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 -- --- --- --- --- --- --- ------- ------- ------- |
name = "git"
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 03 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
def __init__(self, config: "WilyConfig"):
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
"""
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
Instantiate a new Git Archiver.
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
:param config: The wily configuration
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
"""
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
try:
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
self.repo = Repo(config.path)
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
except git.exc.InvalidGitRepositoryError as e:
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
raise InvalidGitRepositoryError from e
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
self.config = config
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
if self.repo.head.is_detached:
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
self.current_branch = self.repo.head.object.hexsha
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
else:
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
self.current_branch = self.repo.active_branch
03 -- 001 001 001 001 002 002 0002.00 0001.00 0000.50 |
assert not self.repo.bare, "Not a Git repository"
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
def revisions(self, path: str, max_revisions: int) -> List[Revision]:
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
"""
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
Get the list of revisions.
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
:param path: the path to target.
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
:param max_revisions: the maximum number of revisions.
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
:return: A list of revisions.
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
"""
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
if self.repo.is_dirty():
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
raise DirtyGitRepositoryError(self.repo.untracked_files)
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
revisions: List[Revision] = []
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
for commit in self.repo.iter_commits(
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
self.current_branch, max_count=max_revisions, reverse=True
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
):
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
tracked_files, tracked_dirs = get_tracked_files_dirs(self.repo, commit)
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
if not commit.parents or not revisions:
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
added_files = tracked_files
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
modified_files = []
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
deleted_files = []
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
else:
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
added_files, modified_files, deleted_files = whatchanged(
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
commit, self.repo.commit(commit.hexsha + "~1")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
)
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
logger.debug(f"For revision {commit.name_rev.split(' ')[0]} found:")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
logger.debug(f"Tracked files: {tracked_files}")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
logger.debug(f"Tracked directories: {tracked_dirs}")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
logger.debug(f"Added files: {added_files}")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
logger.debug(f"Modified files: {modified_files}")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
logger.debug(f"Deleted files: {deleted_files}")
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
rev = Revision(
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
key=commit.name_rev.split(" ")[0],
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
author_name=commit.author.name,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
author_email=commit.author.email,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
date=commit.committed_date,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
message=str(commit.message),
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
tracked_files=tracked_files,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
tracked_dirs=tracked_dirs,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
added_files=added_files,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
modified_files=modified_files,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
deleted_files=deleted_files,
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
)
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
revisions.append(rev)
03 05 004 007 005 007 011 012 0041.51 0083.03 0002.00 |
return revisions[::-1]
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
def checkout(self, revision: Revision, options: Dict[Any, Any]) -> None:
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
"""
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
Checkout a specific revision.
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
:param revision: The revision identifier.
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
:param options: Any additional options.
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
"""
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
rev = revision.key
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
self.repo.git.checkout(rev)
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
def finish(self):
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
"""
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
Clean up any state if processing completed/failed.
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
For git, will checkout HEAD on the original branch when finishing
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
"""
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
self.repo.git.checkout(self.current_branch)
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
self.repo.close()
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
def find(self, search: str) -> Revision:
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
"""
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
Search a string and return a single revision.
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
:param search: The search term.
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
:return: An instance of revision.
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
"""
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
commit = self.repo.commit(search)
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
tracked_files, tracked_dirs = get_tracked_files_dirs(self.repo, commit)
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
if not commit.parents:
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
added_files = tracked_files
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
modified_files = []
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
deleted_files = []
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
else:
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
added_files, modified_files, deleted_files = whatchanged(
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
commit, self.repo.commit(commit.hexsha + "~1")
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
)
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
return Revision(
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
key=commit.name_rev.split(" ")[0],
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
author_name=commit.author.name,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
author_email=commit.author.email,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
date=commit.committed_date,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
message=str(commit.message),
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
tracked_files=tracked_files,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
tracked_dirs=tracked_dirs,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
added_files=added_files,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
modified_files=modified_files,
03 02 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
deleted_files=deleted_files,
-- -- 002 003 002 003 005 005 0011.61 0011.61 0001.00 |
)