diff --git a/VersionControl/BlobObject.py b/VersionControl/BlobObject.py new file mode 100644 index 0000000..0fb6ef0 --- /dev/null +++ b/VersionControl/BlobObject.py @@ -0,0 +1,12 @@ +from VersionControl.GitObject import GitObject + +class BlobObject(GitObject): + + def getObjectType(self): + return "blob" + + def serializeData(self): + return self.blobData + + def deserializeData(self, data): + self.blobData = data \ No newline at end of file diff --git a/VersionControl/CommitObject.py b/VersionControl/CommitObject.py new file mode 100644 index 0000000..12836fe --- /dev/null +++ b/VersionControl/CommitObject.py @@ -0,0 +1,15 @@ +from VersionControl.GitObject import GitObject + +class CommitObject(GitObject): + + def getObjectType(self): + return "commit" + + def serializeData(self): + return GitObject.serialize_commit_object(self.commitData) + + def deserializeData(self, data): + self.commitData = GitObject.parse_commit_object(data) + + def data(self): + return self.commitData \ No newline at end of file diff --git a/VersionControl/GitHistory.py b/VersionControl/GitHistory.py new file mode 100644 index 0000000..a1eb65c --- /dev/null +++ b/VersionControl/GitHistory.py @@ -0,0 +1,154 @@ +from VersionControl import GitObjectFactory +from datetime import datetime +import importlib +if importlib.util.find_spec("termcolor") is not None: + from termcolor import colored +import re + + +def color_out(message, color): + if importlib.util.find_spec("termcolor") is None: + return message + else: + return colored(message, color) + +class GitHistory(object): + + def __init__(self, repo, options={}): + self.repository = repo + self.options = options + + def fillCommitQueue(self, sha, queue): + queue.add(sha) + + obj = GitObjectFactory.GitObjectFactory.factory("commit", self.repository) + contents = obj.read_object(sha).data() + + if "parent" not in contents.keys(): + return + + parent = "" + if type(contents["parent"]) == list: + parent = contents["parent"][0] + else: + parent = contents["parent"] + + self.fillCommitQueue(parent, queue) + + def getLowestCommonAncestor(self, commits): + assert len(commits) > 0 + + queue = CommitQueue() + self.fillCommitQueue(commits[0], queue) + assert queue.size() > 0 + + for other in commits[1:]: + otherQueue = CommitQueue() + self.fillCommitQueue(other, otherQueue) + while(not otherQueue.empty()): + if otherQueue.top() in queue: + break + else: + otherQueue.pop() + + queue = otherQueue + + return queue.top() + + def getLog(self, sha, result=[], LCA=None): + + if sha in result: + return result + result.append(sha) + + obj = GitObjectFactory.GitObjectFactory.factory("commit", self.repository) + contents = obj.read_object(sha).data() + + + if "parent" not in contents.keys(): + return result + + parents = contents["parent"] + if type(parents) != list: + parents = [ parents ] + + if len(parents) > 1: + LCA = self.getLowestCommonAncestor(parents) + + for p in parents: + if p == LCA: + return result + result = self.getLog(p, result, LCA) + + if not LCA is None: + result = self.getLog(LCA, result) + + return result + + def printLog(self, sha_history, colored_output=True): + message = "" + for commit in sha_history: + obj = GitObjectFactory.GitObjectFactory.factory("commit", self.repository) + contents = obj.read_object(commit).data() + if colored_output: + message += color_out("commit " + commit + "\n", 'yellow') + else: + message += "commit " + commit + "\n" + if "parent" in contents.keys(): + if type(contents["parent"]) == list: + message += "Merge:" + for p in contents["parent"]: + message += " " + p[:7] + message += "\n" + header = contents["author"] + CA = re.split('([0-9]+ -[0-9]+)', header) + author = CA[0].strip() + date = int(CA[1].split()[0]) + zone = CA[1].split()[1] + message += "Author: " + author + "\n" + + date = datetime.utcfromtimestamp(date) + message += "Date: " + date.strftime("%a %b %-d %H:%M:%S %Y") + " " + zone + "\n" + message += "\n" + message += " " + contents["short_msg"] + "\n" + message += "\n" + + # Remove last new line + print(message.rstrip('\n')) + + + + + + + +class CommitQueue(object): + + def __init__(self): + self.queue = [] + + def size(self): + return len(self.queue) + + def empty(self): + return len(self.queue) == 0 + + def add(self, item): + self.queue.append(item) + + def pop(self): + return self.queue.pop(0) + + def top(self): + return self.queue[0] + + def pop_until(self, item): + assert item in self.queue, "Item not in queue" + while (len(self.queue) > 0): + if self.top() == item: + return + else: + self.pop() + + def __contains__(self, item): + return item in self.queue \ No newline at end of file diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py new file mode 100644 index 0000000..b5960dd --- /dev/null +++ b/VersionControl/GitObject.py @@ -0,0 +1,154 @@ +import os +import hashlib +import zlib +import re +import collections +from VersionControl import GitObjectFactory + +class GitObject(object): + + def __init__(self, repo, data=None): + self.repository = repo + if data is not None: + self.deserializeData(data) + + def getObjectType(self): + raise NotImplementedError() + + def serializeData(self): + raise NotImplementedError() + + def deserializeData(self, path): + raise NotImplementedError() + + def data(self): + raise NotImplementedError() + + def create_object(self, actually_write=False): + + contents = self.serializeData() + header = self.getObjectType() + " " + str(len(contents)) + '\0' + storage = (header + contents).encode() + + sha = hashlib.sha1(storage).hexdigest() + + if actually_write: + filePath = os.path.join(self.repository.gitDir, "objects", sha[0:2], sha[2:]) + os.makedirs(os.path.dirname(filePath)) + with open(filePath, "wb") as f: + f.write(zlib.compress(storage)) + + return sha + + def read_object(self, sha_hash): + + repoFilePath = os.path.join(self.repository.gitDir, "objects", sha_hash[0:2], sha_hash[2:]) + + contents = "" + + with open(repoFilePath, "rb") as f: + raw = zlib.decompress(f.read()) + + x = raw.find(b' ') + obj_format = raw[0:x].decode('ascii') + assert obj_format in ['blob', 'commit'] + + y = raw.find(b'\x00', x) + obj_size = int(raw[x:y].decode('ascii')) + + contents = raw[y+1:].decode('ascii') + + if len(contents) != obj_size: + raise RuntimeError("Malformed Object {0}: bad length".format(sha_hash)) + + return GitObjectFactory.GitObjectFactory.factory(obj_format, self.repository, contents) + + @staticmethod + def parse_commit_object(data): + # Commit object consists of two parts. The first part is a key value pair list + # where keys and values are separated by a space. The key starts at the line begining + # until the first space. The value is all that remains. Values can continue after line + # ends where a space will start the next line. + # The second part consists of a short commit message and an optional long commit message + + # Split the lines of data if the character following the newline is not a space + # Keep the split string because it will contain the first character of a key + SD = re.split("(\n[^ ])", data) + + # Add the first character of the key back to that key value pair + splitData = [""] + for item in SD: + if item[0] != '\n': + splitData[-1] += item + else: + splitData.append(item[1:]) + + # Add all key value pairs along with long and short messages to an ordered + # dictionary + result = collections.OrderedDict() + key_values_done = False + for item in splitData: + + # The commit messages start after a blank line. This shows up as the first + # character being a new line + if item[0] == '\n': + key_values_done = True + + if not key_values_done: + # Split the key value pair by the key and the remaining + split_item = item.split(" ", 1) + key = split_item[0] + value = split_item[1] + + # Take care of line continuation + value = value.replace("\n ", "\n") + + # Don't overwrite data in the dictionary. Append it + if key in result: + if type(result[key]) == list: + result[key].append(value) + else: + result[key] = [ result[key], value ] + else: + result[key] = value + else: + # Add short message and keep going + if "short_msg" not in result: + result["short_msg"] = item.strip() + continue + + # Add all remaining lines as long message + if "long_msg" not in result: + result["long_msg"] = item.strip() + else: + result["long_msg"] += '\n' + item.strip() + + # last_key = next(reversed(result)) + # result[last_key] += '\n' + + return result + + @staticmethod + def serialize_commit_object(data): + + result = "" + + for key, value in data.items(): + + if key == "short_msg" or key == "long_msg": + continue + + if type(value) != list: + value = [ value ] + + for v in value: + result += key + " " + v.replace("\n", "\n ") + "\n" + + if "short_msg" in data: + result += "\n" + data["short_msg"] + '\n' + if "long_msg" in data: + result += "\n" + data["long_msg"] + + # result += '\n' + + return result \ No newline at end of file diff --git a/VersionControl/GitObjectFactory.py b/VersionControl/GitObjectFactory.py new file mode 100644 index 0000000..222f972 --- /dev/null +++ b/VersionControl/GitObjectFactory.py @@ -0,0 +1,12 @@ +from VersionControl import BlobObject, CommitObject + +class GitObjectFactory(object): + + @staticmethod + def factory(type, repository, data=None): + if type == "blob": + return BlobObject.BlobObject(repository, data) + elif type == "commit": + return CommitObject.CommitObject(repository, data) + else: + raise RuntimeError("Unknown Object Type: "+str(type)) \ No newline at end of file diff --git a/VersionControl/GitRepository.py b/VersionControl/GitRepository.py index 65b487b..f3a00f6 100644 --- a/VersionControl/GitRepository.py +++ b/VersionControl/GitRepository.py @@ -1,7 +1,7 @@ import os, sys import configparser -class GitRepository(): +class GitRepository(object): def __init__(self, path): self.workTree = os.path.abspath(path) @@ -31,6 +31,8 @@ def initializeGitDir(self): config = self.getDefaultConfig() config.write(f) + self.initialized = True + def getDefaultConfig(self): result = configparser.ConfigParser() @@ -41,6 +43,37 @@ def getDefaultConfig(self): return result + def getHeadPath(self): + if not os.path.isdir(self.gitDir): + raise RuntimeError("Uninitialized repository") + + headPath = "" + with open(os.path.join(self.gitDir, "HEAD")) as f: + headPath = f.read().split()[1].strip() + + return headPath + + def setHeadCommit(self, sha): + if not os.path.isdir(self.gitDir): + raise RuntimeError("Uninitialized repository") + + headPath = self.getHeadPath() + + with open(os.path.join(self.gitDir, headPath), "w") as f: + f.write(sha) + + def getHeadCommit(self): + if not os.path.isdir(self.gitDir): + raise RuntimeError("Uninitialized repository") + + headPath = self.getHeadPath() + + sha = "" + with open(os.path.join(self.gitDir, headPath)) as f: + sha = f.read().strip() + + return sha + @staticmethod def find_repo(path): path = os.path.abspath(path) diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index e09c30f..5ee15c1 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -1,8 +1,10 @@ import argparse -import sys +import sys, os from VersionControl import GitRepository +from VersionControl import GitObjectFactory +from VersionControl import GitHistory -class wyag(): +class wyag(object): def __init__(self): pass @@ -15,6 +17,19 @@ def processCLI(self, arg_list=None): init_parser = subparsers.add_parser('init', help='Initialize Repo') init_parser.add_argument('path', help='Where to initialize repo') + hash_obj_parser = subparsers.add_parser('hash-object', help='Hash file into object type') + hash_obj_parser.add_argument('path', help='Path to file') + hash_obj_parser.add_argument('-w', help='Actually write object file', dest='write_obj', action='store_true') + hash_obj_parser.add_argument('-t', help='Type of Object', dest='object_type', choices=['blob', 'commit'], default='blob') + + cat_file_parser = subparsers.add_parser('cat-file', help='Output contents of object') + cat_file_parser.add_argument('hash', help='Hash of object') + cat_file_parser.add_argument('-t', help="Type of Object", dest="object_type", choices=['blob', 'commit'], default='blob') + + log_parser = subparsers.add_parser('log', help="Output Commit History") + log_parser.add_argument('hash', help='Hash where to start history', nargs='?', default='head') + log_parser.add_argument('--no-color', help='Suppress color output', dest='color_out', action='store_false') + if arg_list is not None and len(arg_list) < 1: parser.print_help() sys.exit(1) @@ -29,4 +44,26 @@ def run(self, arg_list=None): if cli_args["command"] == "init": repo = GitRepository.GitRepository(cli_args['path']) - repo.initializeGitDir() \ No newline at end of file + repo.initializeGitDir() + + elif cli_args["command"] == "hash-object": + repo = GitRepository.GitRepository.find_repo(cli_args['path']) + fileContents = "" + with open(cli_args['path']) as f: + fileContents = f.read() + obj = GitObjectFactory.GitObjectFactory.factory(cli_args["object_type"], repo, fileContents) + print(obj.create_object(cli_args['write_obj'])) + + elif cli_args["command"] == "cat-file": + repo = GitRepository.GitRepository.find_repo(os.getcwd()) + obj = GitObjectFactory.GitObjectFactory.factory(cli_args["object_type"], repo) + print(obj.read_object(cli_args['hash']).serializeData(), end='') + + elif cli_args["command"] == "log": + repo = GitRepository.GitRepository.find_repo(os.getcwd()) + history = GitHistory.GitHistory(repo) + sha = cli_args["hash"] + if sha == "head": + sha = repo.getHeadCommit() + log = history.getLog(sha) + history.printLog(log, colored_output=cli_args["color_out"]) diff --git a/VersionControl/tests/test_Repository.py b/VersionControl/tests/test_Repository.py index 3fe039e..458fc56 100644 --- a/VersionControl/tests/test_Repository.py +++ b/VersionControl/tests/test_Repository.py @@ -1,6 +1,6 @@ import pytest import os -from VersionControl import GitRepository +from VersionControl import GitRepository, GitObjectFactory def test_init_repo(tmpdir): repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) @@ -89,4 +89,31 @@ def test_find_repo(tmpdir): os.chdir(os.path.join(tmpdir, "myTest", "dir1", "dir11", "dir13")) assert os.getcwd() == os.path.join(tmpdir, "myTest", "dir1", "dir11", "dir13") result = GitRepository.GitRepository.find_repo(os.getcwd()) - assert result == repo \ No newline at end of file + assert result == repo + +def test_set_head(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + + with pytest.raises(RuntimeError): + repo.setHeadCommit("blah") + + repo.initializeGitDir() + + commitData = ["tree aff72e7367c3ae1928decc25272ec334a805e618\n" + \ + "author Kevin J. Dugan 1560083980 -0400\n" + \ + "committer Kevin J. Dugan 1560083980 -0400\n" + \ + "\n" + \ + "Initial Commit\n", "d228dfd0601080af1af564eb7a3bc6fbb7a2696f"] + + obj = GitObjectFactory.GitObjectFactory.factory("commit", repo, commitData[0]) + sha = obj.create_object(True) + assert sha == commitData[1] + + headPath = os.path.join(tmpdir, "myTest", ".git", "refs", "heads", "master") + assert not os.path.isfile(headPath) + repo.setHeadCommit(sha) + assert os.path.isfile(headPath) + with open(headPath) as f: + assert sha == f.read().strip() + + assert repo.getHeadCommit() == sha \ No newline at end of file diff --git a/VersionControl/tests/test_git_history.py b/VersionControl/tests/test_git_history.py new file mode 100644 index 0000000..21e0c4f --- /dev/null +++ b/VersionControl/tests/test_git_history.py @@ -0,0 +1,165 @@ +from VersionControl import GitHistory, GitRepository +from VersionControl import CommitObject +import pytest +import os + +def test_commit_queue(): + + queue = GitHistory.CommitQueue() + assert queue.size() == 0 + assert queue.empty() + + queue.add("first") + queue.add("second") + queue.add("third") + queue.add("fourth") + queue.add("fifth") + queue.add("sixth") + assert queue.size() == 6 + + assert queue.pop() == "first" + assert queue.top() == "second" + + assert "second" in queue + assert "fifth" in queue + assert not "first" in queue + + with pytest.raises(AssertionError): + queue.pop_until("feet") + + queue.pop_until("fourth") + assert queue.top() == "fourth" + +def test_fill_commit_queue(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + historyData = git_commit_history_data() + for commit in historyData: + obj = CommitObject.CommitObject(repo, commit[0]) + sha = obj.create_object(True) + assert sha == commit[1] + + queue = GitHistory.CommitQueue() + history = GitHistory.GitHistory(repo) + history.fillCommitQueue(historyData[-1][1], queue) + assert queue.size() == 5 + assert queue.top() == historyData[-1][1] + +def test_base_commit(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + historyData = git_commit_history_data() + for commit in historyData: + obj = CommitObject.CommitObject(repo, commit[0]) + sha = obj.create_object(True) + assert sha == commit[1] + + sha1 = historyData[3][1] + sha2 = historyData[4][1] + base = historyData[1][1] + + history = GitHistory.GitHistory(repo) + result = history.getLowestCommonAncestor(commits=[sha1, sha2]) + assert result == base + + result = history.getLowestCommonAncestor(commits=[sha1]) + assert result == sha1 + + +def test_log(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + historyData = git_commit_history_data() + for commit in historyData: + obj = CommitObject.CommitObject(repo, commit[0]) + sha = obj.create_object(True) + assert sha == commit[1] + + head = historyData[-1][1] + # print(head) + + history = GitHistory.GitHistory(repo) + shaList = history.getLog(head) + print("\nLog") + for result, expected in zip(shaList, reversed(historyData)): + print("Result: "+result+" Expected: "+expected[1]) + + assert len(shaList) == len(historyData) + for result, expected in zip(shaList,reversed(historyData)): + assert result == expected[1] + + history.printLog(shaList) + + + + + + + + + +############################################################################### +# Data Setup +############################################################################### +def git_commit_history_data(): + history = [] + history.append(["tree aff72e7367c3ae1928decc25272ec334a805e618\n" + \ + "author Kevin J. Dugan 1560083980 -0400\n" + \ + "committer Kevin J. Dugan 1560083980 -0400\n" + \ + "\n" + \ + "Initial Commit\n", "d228dfd0601080af1af564eb7a3bc6fbb7a2696f"]) + + + history.append(["tree 18e156c8acb2081a9b300796b8672f27837e3961\n" + \ + "parent d228dfd0601080af1af564eb7a3bc6fbb7a2696f\n" + \ + "author Kevin J. Dugan 1560084040 -0400\n" + \ + "committer Kevin J. Dugan 1560084040 -0400\n" + \ + "\n" + \ + "Added new line\n", "30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098"]) + + + history.append(["tree 24cc7c0ead06780c12ae9ae79fc7fb69c0f054ee\n" + \ + "parent 30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098\n" + \ + "author Kevin J. Dugan 1560084096 -0400\n" + \ + "committer Kevin J. Dugan 1560084096 -0400\n" + \ + "\n" + \ + "Added new file\n", "6ed18ac3b2d77272bf240f313901ef75076d6377"]) + + + history.append(["tree c8cd9c7e731de0feb53cd0f0a60ebe39d30ebfd8\n" + \ + "parent 6ed18ac3b2d77272bf240f313901ef75076d6377\n" + \ + "author Kevin J. Dugan 1560084124 -0400\n" + \ + "committer Kevin J. Dugan 1560084124 -0400\n" + \ + "\n" + \ + "Added new line\n", "17c06afad13f728e4e31ae85a534a5cf73e2bd76"]) + + + history.append(["tree 4fdedfd76570511fb0e43153b8cfe2aba896b009\n" + \ + "parent 30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098\n" + \ + "author Kevin J. Dugan 1560084362 -0400\n" + \ + "committer Kevin J. Dugan 1560084362 -0400\n" + \ + "\n" + \ + "Added new lines\n", "bbfbe5740cb7180100e86504779d38173d2247cb"]) + + + history.append(["tree 24cb97583ae8ef3e046c8e7f8d78aea8a0986e20\n" + \ + "parent bbfbe5740cb7180100e86504779d38173d2247cb\n" + \ + "parent 17c06afad13f728e4e31ae85a534a5cf73e2bd76\n" + \ + "author Kevin J. Dugan 1560084417 -0400\n" + \ + "committer Kevin J. Dugan 1560084417 -0400\n" + \ + "\n" + \ + "Merge branch 'new-file'\n", "af54843b4fa85db56ed9140b5a39ec2df744fc4b"]) + + + history.append(["tree 0fef7a0dc4cd421c3645c21f878b76205f78c3b1\n" + \ + "parent af54843b4fa85db56ed9140b5a39ec2df744fc4b\n" + \ + "author Kevin J. Dugan 1560084474 -0400\n" + \ + "committer Kevin J. Dugan 1560084474 -0400\n" + \ + "\n" + \ + "added new lines\n", "075c7e021c0d2e4a43f01a2e848daf605ed4e65f"]) + + + return history \ No newline at end of file diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py new file mode 100644 index 0000000..47dc052 --- /dev/null +++ b/VersionControl/tests/test_git_object.py @@ -0,0 +1,148 @@ +import pytest +from VersionControl import GitRepository, BlobObject, GitObject, CommitObject +import os + +def test_create_blob_object(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + # Create File + contents = 'blah\nblahblah\nblah blah blah\n\nblah\n' + expected_hash = '5e9548362714d1cef162e97a23a7a42f7311e54d' + + obj = BlobObject.BlobObject(repo, contents) + sha = obj.create_object(False) + + assert sha == expected_hash + + sha = obj.create_object(True) + assert os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "objects", expected_hash[0:2], expected_hash[2:])) + +def test_read_blob_object(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + # Create File + contents = 'blah\nblahblah\nblah blah blah\n\nblah\n' + expected_hash = '5e9548362714d1cef162e97a23a7a42f7311e54d' + + obj = BlobObject.BlobObject(repo, contents) + sha = obj.create_object(True) + + result = obj.read_object(sha).serializeData() + assert result == contents + +def test_parse_commit_object(): + + # Standard Commit + contents, expected_hash = get_simple_commit_data() + + result = GitObject.GitObject.parse_commit_object(contents) + + assert "tree" in result + assert result["tree"] == 'aa2fd36eabc0cdb0c629c3d164d81dc31c2aa8fb' + assert "parent" in result + assert result["parent"] == '6d2f5894d033a5c19cd8d3a41ab1626ab272ed51' + assert "short_msg" in result + assert result["short_msg"] == 'Adding readme' + assert "long_msg" not in result + + serial_result = GitObject.GitObject.serialize_commit_object(result) + assert serial_result == contents + + # More complicated commit + contents, expected_hash = get_complex_commit_data() + + result = GitObject.GitObject.parse_commit_object(contents) + + assert "tree" in result + assert result["tree"] == "025b46d991b8602e229fa477dbc50a98ee050dcf" + assert "parent" in result + assert len(result["parent"]) == 2 + for p,expt in zip (result["parent"], ["a33e731eb4d600b08dcd34fea6fe45cecc7958a0", "f4f16ee537a618502fac0f6a0db822ddd3b45b12"]): + assert p == expt + assert "gpgsig" in result + assert result["gpgsig"] == "-----BEGIN PGP SIGNATURE-----\n" + \ + "\n" + \ + "wsBcBAABCAAQBQJc/Ac+CRBK7hj4Ov3rIwAAdHIIAJVYNA8ZCS8Gl/ckHB+SSwIt\n" + \ + "Uu78eWzEcR/kg229iBJWhUix0M3q0ku40aMwB5YjxVvRWNUE9OYiXqqYMvRHWopO\n" + \ + "KEHK43z1SVAmAUUUfxHnqj/w72qNlSVMsNGJVMb45jlmRoaD4zt5tvnuqqAY9yV0\n" + \ + "xKtAIwjRvkX5zK9FRXPzP1e9PULLvcSXI8GJyUEi8gdryqipxyKP0B9t23tUCZLA\n" + \ + "XyAMBiQLYfo8LFvsPwS8qSeuv2JHiyBLMEeqGc90KJzXQU7vUsX0Xp9HmkG6TBCa\n" + \ + "655BsGkxwC3y04Png6HcY5nX3Y0XNkAPCjx5fnoRxYyZC92jnREs8rZ3odc5jRo=\n" + \ + "=4fKp\n" + \ + "-----END PGP SIGNATURE-----\n" + + + assert "short_msg" in result + assert result["short_msg"] == "Merge pull request #1 from kevindugan/travis-ci" + assert "long_msg" in result + assert result["long_msg"] == "Adding ci script" + + serial_result = GitObject.GitObject.serialize_commit_object(result) + assert serial_result == contents + +def test_create_commit_object(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + contents, expected_hash = get_complex_commit_data() + + obj = CommitObject.CommitObject(repo, contents) + sha = obj.create_object(False) + + assert sha == expected_hash + + sha = obj.create_object(True) + assert os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "objects", expected_hash[0:2], expected_hash[2:])) + +def test_read_commit_object(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + # Create File + contents, expected_hash = get_complex_commit_data() + + obj = CommitObject.CommitObject(repo, contents) + sha = obj.create_object(True) + + result = obj.read_object(sha).serializeData() + assert result == contents + + + + + +############################################################################### +# Data Setup +############################################################################### +def get_simple_commit_data(): + return "tree aa2fd36eabc0cdb0c629c3d164d81dc31c2aa8fb\n" + \ + "parent 6d2f5894d033a5c19cd8d3a41ab1626ab272ed51\n" + \ + "author Kevin J. Dugan 1559970810 -0400\n" + \ + "committer Kevin J. Dugan 1559970810 -0400\n" + \ + "\n" + \ + "Adding readme\n", "a62df6acda73871958f89e0346c97284d1a14f23" + +def get_complex_commit_data(): + return "tree 025b46d991b8602e229fa477dbc50a98ee050dcf\n" + \ + "parent a33e731eb4d600b08dcd34fea6fe45cecc7958a0\n" + \ + "parent f4f16ee537a618502fac0f6a0db822ddd3b45b12\n" + \ + "author Kevin Dugan 1560020798 -0400\n" + \ + "committer GitHub 1560020798 -0400\n" + \ + "gpgsig -----BEGIN PGP SIGNATURE-----\n" + \ + " \n" + \ + " wsBcBAABCAAQBQJc/Ac+CRBK7hj4Ov3rIwAAdHIIAJVYNA8ZCS8Gl/ckHB+SSwIt\n" + \ + " Uu78eWzEcR/kg229iBJWhUix0M3q0ku40aMwB5YjxVvRWNUE9OYiXqqYMvRHWopO\n" + \ + " KEHK43z1SVAmAUUUfxHnqj/w72qNlSVMsNGJVMb45jlmRoaD4zt5tvnuqqAY9yV0\n" + \ + " xKtAIwjRvkX5zK9FRXPzP1e9PULLvcSXI8GJyUEi8gdryqipxyKP0B9t23tUCZLA\n" + \ + " XyAMBiQLYfo8LFvsPwS8qSeuv2JHiyBLMEeqGc90KJzXQU7vUsX0Xp9HmkG6TBCa\n" + \ + " 655BsGkxwC3y04Png6HcY5nX3Y0XNkAPCjx5fnoRxYyZC92jnREs8rZ3odc5jRo=\n" + \ + " =4fKp\n" + \ + " -----END PGP SIGNATURE-----\n" + \ + " \n" + \ + "\n" + \ + "Merge pull request #1 from kevindugan/travis-ci\n" + \ + "\n" + \ + "Adding ci script", "0b2076b2607785d7aa94eb7fd63689f679967c04" + diff --git a/VersionControl/tests/test_wyag.py b/VersionControl/tests/test_wyag.py index fcf39aa..e57fc71 100644 --- a/VersionControl/tests/test_wyag.py +++ b/VersionControl/tests/test_wyag.py @@ -9,4 +9,222 @@ def test_init_command(tmpdir): assert os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "HEAD")) with open(os.path.join(tmpdir, "myTest", ".git", "HEAD")) as f: - assert f.read().strip() == "ref: refs/heads/master" \ No newline at end of file + assert f.read().strip() == "ref: refs/heads/master" + +def test_blob_objects(tmpdir, capsys): + + myGit = WyagLib.wyag() + myGit.run(['init', os.path.join(tmpdir, "myTest")]) + + # Create file + contents = "blah blah\n\nblah\nblahblahbla\n\n" + filePath = os.path.join(tmpdir, "myTest", "dir1", "testFile.txt") + os.makedirs(os.path.dirname(filePath)) + with open(filePath, "w") as f: + f.write(contents) + + # Hash without writing + expected_hash = '50067d0b090dc8bec4e78788eb7db8025092b6e0' + myGit.run(['hash-object', filePath]) + captured = capsys.readouterr() + assert captured.out.strip() == expected_hash + + assert not os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "objects", expected_hash[0:2], expected_hash[2:])) + + # Hash and write file + myGit.run(['hash-object', '-w', filePath]) + captured = capsys.readouterr() + assert captured.out.strip() == expected_hash + + assert os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "objects", expected_hash[0:2], expected_hash[2:])) + + # Get contents of file + os.chdir(os.path.join(tmpdir, "myTest")) + myGit.run(['cat-file', expected_hash]) + captured = capsys.readouterr() + assert captured.out == contents + +def test_commit_objects(tmpdir, capsys): + + myGit = WyagLib.wyag() + myGit.run(['init', os.path.join(tmpdir, "myTest")]) + + # Create file + contents = "tree 025b46d991b8602e229fa477dbc50a98ee050dcf\n" + \ + "parent a33e731eb4d600b08dcd34fea6fe45cecc7958a0\n" + \ + "parent f4f16ee537a618502fac0f6a0db822ddd3b45b12\n" + \ + "author Kevin Dugan 1560020798 -0400\n" + \ + "committer GitHub 1560020798 -0400\n" + \ + "gpgsig -----BEGIN PGP SIGNATURE-----\n" + \ + " \n" + \ + " wsBcBAABCAAQBQJc/Ac+CRBK7hj4Ov3rIwAAdHIIAJVYNA8ZCS8Gl/ckHB+SSwIt\n" + \ + " Uu78eWzEcR/kg229iBJWhUix0M3q0ku40aMwB5YjxVvRWNUE9OYiXqqYMvRHWopO\n" + \ + " KEHK43z1SVAmAUUUfxHnqj/w72qNlSVMsNGJVMb45jlmRoaD4zt5tvnuqqAY9yV0\n" + \ + " xKtAIwjRvkX5zK9FRXPzP1e9PULLvcSXI8GJyUEi8gdryqipxyKP0B9t23tUCZLA\n" + \ + " XyAMBiQLYfo8LFvsPwS8qSeuv2JHiyBLMEeqGc90KJzXQU7vUsX0Xp9HmkG6TBCa\n" + \ + " 655BsGkxwC3y04Png6HcY5nX3Y0XNkAPCjx5fnoRxYyZC92jnREs8rZ3odc5jRo=\n" + \ + " =4fKp\n" + \ + " -----END PGP SIGNATURE-----\n" + \ + " \n" + \ + "\n" + \ + "Merge pull request #1 from kevindugan/travis-ci\n" + \ + "\n" + \ + "Adding ci script" + filePath = os.path.join(tmpdir, "myTest", "dir1", "testFile.txt") + os.makedirs(os.path.dirname(filePath)) + with open(filePath, "w") as f: + f.write(contents) + + # Hash without writing + expected_hash = '0b2076b2607785d7aa94eb7fd63689f679967c04' + myGit.run(['hash-object', '-t', 'commit', filePath]) + captured = capsys.readouterr() + assert captured.out.strip() == expected_hash + + assert not os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "objects", expected_hash[0:2], expected_hash[2:])) + + # Hash and write file + myGit.run(['hash-object', '-w', '-t', 'commit', filePath]) + captured = capsys.readouterr() + assert captured.out.strip() == expected_hash + + assert os.path.isfile(os.path.join(tmpdir, "myTest", ".git", "objects", expected_hash[0:2], expected_hash[2:])) + + # Get contents of file + os.chdir(os.path.join(tmpdir, "myTest")) + myGit.run(['cat-file', expected_hash]) + captured = capsys.readouterr() + assert captured.out == contents + +def test_log_cmd(tmpdir, capsys): + + myGit = WyagLib.wyag() + myGit.run(['init', os.path.join(tmpdir, "myTest")]) + + commitHistory = git_commit_history_data() + for item in commitHistory: + with open(os.path.join(tmpdir, "myTest", "hashObject.txt"), "w") as f: + f.write(item[0]) + myGit.run(['hash-object', '-w', '-t', 'commit', os.path.join(tmpdir, "myTest", "hashObject.txt")]) + captured = capsys.readouterr() + assert captured.out.strip() == item[1] + + # Change into generated repo + os.chdir(os.path.join(tmpdir, "myTest")) + + # Create HEAD + with open(os.path.join(tmpdir, "myTest", ".git", "refs", "heads", "master"), "w") as f: + f.write(commitHistory[-1][1]) + + myGit.run(['log', '--no-color']) + captured = capsys.readouterr() + log_result = captured.out.strip() + assert log_result == get_log_output_plain() + + + +def git_commit_history_data(): + history = [] + history.append(["tree aff72e7367c3ae1928decc25272ec334a805e618\n" + \ + "author Kevin J. Dugan 1560083980 -0400\n" + \ + "committer Kevin J. Dugan 1560083980 -0400\n" + \ + "\n" + \ + "Initial Commit\n", "d228dfd0601080af1af564eb7a3bc6fbb7a2696f"]) + + + history.append(["tree 18e156c8acb2081a9b300796b8672f27837e3961\n" + \ + "parent d228dfd0601080af1af564eb7a3bc6fbb7a2696f\n" + \ + "author Kevin J. Dugan 1560084040 -0400\n" + \ + "committer Kevin J. Dugan 1560084040 -0400\n" + \ + "\n" + \ + "Added new line\n", "30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098"]) + + + history.append(["tree 24cc7c0ead06780c12ae9ae79fc7fb69c0f054ee\n" + \ + "parent 30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098\n" + \ + "author Kevin J. Dugan 1560084096 -0400\n" + \ + "committer Kevin J. Dugan 1560084096 -0400\n" + \ + "\n" + \ + "Added new file\n", "6ed18ac3b2d77272bf240f313901ef75076d6377"]) + + + history.append(["tree c8cd9c7e731de0feb53cd0f0a60ebe39d30ebfd8\n" + \ + "parent 6ed18ac3b2d77272bf240f313901ef75076d6377\n" + \ + "author Kevin J. Dugan 1560084124 -0400\n" + \ + "committer Kevin J. Dugan 1560084124 -0400\n" + \ + "\n" + \ + "Added new line\n", "17c06afad13f728e4e31ae85a534a5cf73e2bd76"]) + + + history.append(["tree 4fdedfd76570511fb0e43153b8cfe2aba896b009\n" + \ + "parent 30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098\n" + \ + "author Kevin J. Dugan 1560084362 -0400\n" + \ + "committer Kevin J. Dugan 1560084362 -0400\n" + \ + "\n" + \ + "Added new lines\n", "bbfbe5740cb7180100e86504779d38173d2247cb"]) + + + history.append(["tree 24cb97583ae8ef3e046c8e7f8d78aea8a0986e20\n" + \ + "parent bbfbe5740cb7180100e86504779d38173d2247cb\n" + \ + "parent 17c06afad13f728e4e31ae85a534a5cf73e2bd76\n" + \ + "author Kevin J. Dugan 1560084417 -0400\n" + \ + "committer Kevin J. Dugan 1560084417 -0400\n" + \ + "\n" + \ + "Merge branch 'new-file'\n", "af54843b4fa85db56ed9140b5a39ec2df744fc4b"]) + + + history.append(["tree 0fef7a0dc4cd421c3645c21f878b76205f78c3b1\n" + \ + "parent af54843b4fa85db56ed9140b5a39ec2df744fc4b\n" + \ + "author Kevin J. Dugan 1560084474 -0400\n" + \ + "committer Kevin J. Dugan 1560084474 -0400\n" + \ + "\n" + \ + "added new lines\n", "075c7e021c0d2e4a43f01a2e848daf605ed4e65f"]) + + + return history + +def get_log_output_plain(): + message = "" + message += "commit 075c7e021c0d2e4a43f01a2e848daf605ed4e65f\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:47:54 2019 -0400\n" + message += "\n" + message += " added new lines\n" + message += "\n" + message += "commit af54843b4fa85db56ed9140b5a39ec2df744fc4b\n" + message += "Merge: bbfbe57 17c06af\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:46:57 2019 -0400\n" + message += "\n" + message += " Merge branch 'new-file'\n" + message += "\n" + message += "commit bbfbe5740cb7180100e86504779d38173d2247cb\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:46:02 2019 -0400\n" + message += "\n" + message += " Added new lines\n" + message += "\n" + message += "commit 17c06afad13f728e4e31ae85a534a5cf73e2bd76\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:42:04 2019 -0400\n" + message += "\n" + message += " Added new line\n" + message += "\n" + message += "commit 6ed18ac3b2d77272bf240f313901ef75076d6377\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:41:36 2019 -0400\n" + message += "\n" + message += " Added new file\n" + message += "\n" + message += "commit 30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:40:40 2019 -0400\n" + message += "\n" + message += " Added new line\n" + message += "\n" + message += "commit d228dfd0601080af1af564eb7a3bc6fbb7a2696f\n" + message += "Author: Kevin J. Dugan \n" + message += "Date: Sun Jun 9 12:39:40 2019 -0400\n" + message += "\n" + message += " Initial Commit" + return message \ No newline at end of file