From 2a14e83d2f8b8c429dc5a54fce4530270dc0b1d3 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 15:20:15 -0400 Subject: [PATCH 01/18] Inheriting from python object --- VersionControl/GitRepository.py | 2 +- VersionControl/WyagLib.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VersionControl/GitRepository.py b/VersionControl/GitRepository.py index 65b487b..7092f22 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) diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index e09c30f..ae03f4b 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -2,7 +2,7 @@ import sys from VersionControl import GitRepository -class wyag(): +class wyag(object): def __init__(self): pass From 7c732ad55a555dfd533db2f2155c57d26293ebd2 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 18:09:04 -0400 Subject: [PATCH 02/18] Adding git blob objects --- VersionControl/GitObject.py | 52 +++++++++++++++++++++++++ VersionControl/WyagLib.py | 11 +++++- VersionControl/tests/test_git_object.py | 41 +++++++++++++++++++ VersionControl/tests/test_wyag.py | 29 +++++++++++++- 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 VersionControl/GitObject.py create mode 100644 VersionControl/tests/test_git_object.py diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py new file mode 100644 index 0000000..307fcee --- /dev/null +++ b/VersionControl/GitObject.py @@ -0,0 +1,52 @@ +import os +import hashlib +import zlib + +class GitObject(object): + + def __init__(self, repo): + self.repository = repo + + def create_object(self, path, actually_write=False): + if not os.path.isabs(path): + raise RuntimeError("Expected Absolute Path") + + contents = "" + with open(path) as f: + contents = f.read() + + header = "blob " + 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') + + 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)) + + assert obj_format == 'blob' + + return contents \ No newline at end of file diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index ae03f4b..8dd3d3f 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -1,6 +1,7 @@ import argparse import sys from VersionControl import GitRepository +from VersionControl import GitObject class wyag(object): @@ -15,6 +16,10 @@ 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') + if arg_list is not None and len(arg_list) < 1: parser.print_help() sys.exit(1) @@ -29,4 +34,8 @@ 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']) + obj = GitObject.GitObject(repo) + print(obj.create_object(cli_args['path'], cli_args['write_obj'])) \ 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..a618213 --- /dev/null +++ b/VersionControl/tests/test_git_object.py @@ -0,0 +1,41 @@ +import pytest +from VersionControl import GitRepository, GitObject +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' + + filePath = os.path.join(tmpdir, "myTest", "test.txt") + with open(filePath, "w") as f: + f.write(contents) + + obj = GitObject.GitObject(repo) + sha = obj.create_object(filePath, False) + + assert sha == expected_hash + + sha = obj.create_object(filePath, 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' + + filePath = os.path.join(tmpdir, "myTest", "test.txt") + with open(filePath, "w") as f: + f.write(contents) + + obj = GitObject.GitObject(repo) + sha = obj.create_object(filePath, True) + + result = obj.read_object(sha) + assert result == contents diff --git a/VersionControl/tests/test_wyag.py b/VersionControl/tests/test_wyag.py index fcf39aa..f57fb54 100644 --- a/VersionControl/tests/test_wyag.py +++ b/VersionControl/tests/test_wyag.py @@ -9,4 +9,31 @@ 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:])) From e0b862c7f277a48b69d41609f30cd3f263821e00 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 19:25:57 -0400 Subject: [PATCH 03/18] Converting to abspath --- VersionControl/GitObject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 307fcee..b7679f9 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -9,7 +9,7 @@ def __init__(self, repo): def create_object(self, path, actually_write=False): if not os.path.isabs(path): - raise RuntimeError("Expected Absolute Path") + path = os.path.abspath(path) contents = "" with open(path) as f: From 7f2a95f898283d4c770309201dbcb215a8caee51 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 19:42:01 -0400 Subject: [PATCH 04/18] Adding output of blob object Extra long commit message for testing purposes.. This should show as much longer in logs --- VersionControl/WyagLib.py | 11 +++++++++-- VersionControl/tests/test_wyag.py | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index 8dd3d3f..17544ea 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -1,5 +1,5 @@ import argparse -import sys +import sys, os from VersionControl import GitRepository from VersionControl import GitObject @@ -20,6 +20,9 @@ def processCLI(self, arg_list=None): 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') + cat_file_parser = subparsers.add_parser('cat-file', help='Output contents of object') + cat_file_parser.add_argument('hash', help='Hash of object') + if arg_list is not None and len(arg_list) < 1: parser.print_help() sys.exit(1) @@ -38,4 +41,8 @@ def run(self, arg_list=None): elif cli_args["command"] == "hash-object": repo = GitRepository.GitRepository.find_repo(cli_args['path']) obj = GitObject.GitObject(repo) - print(obj.create_object(cli_args['path'], cli_args['write_obj'])) \ No newline at end of file + print(obj.create_object(cli_args['path'], cli_args['write_obj'])) + elif cli_args["command"] == "cat-file": + repo = GitRepository.GitRepository.find_repo(os.getcwd()) + obj = GitObject.GitObject(repo) + print(obj.read_object(cli_args['hash']), end='') \ No newline at end of file diff --git a/VersionControl/tests/test_wyag.py b/VersionControl/tests/test_wyag.py index f57fb54..cb321a4 100644 --- a/VersionControl/tests/test_wyag.py +++ b/VersionControl/tests/test_wyag.py @@ -37,3 +37,9 @@ def test_blob_objects(tmpdir, capsys): 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 From 37e125c558f1938e295161e1570eec686505ac77 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 21:51:20 -0400 Subject: [PATCH 05/18] Adding commit parser --- VersionControl/GitObject.py | 66 ++++++++++++++++++++- VersionControl/tests/test_git_object.py | 76 +++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index b7679f9..5fcbe7f 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -1,6 +1,8 @@ import os import hashlib import zlib +import re +import collections class GitObject(object): @@ -49,4 +51,66 @@ def read_object(self, sha_hash): assert obj_format == 'blob' - return contents \ No newline at end of file + return 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() + + return result \ No newline at end of file diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index a618213..b932307 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -39,3 +39,79 @@ def test_read_blob_object(tmpdir): result = obj.read_object(sha) assert result == contents + +def test_parse_commit_object(): + + # Standard Commit + contents = "tree 7a2808c8a1d6b89d2072260593fe6341b6be0519\n" + \ + "parent e0b862c7f277a48b69d41609f30cd3f263821e00\n" + \ + "author Kevin J. Dugan 1560037321 -0400\n" + \ + "committer Kevin J. Dugan 1560040021 -0400\n" + \ + "\n" + \ + "Adding output of blob object\n" + \ + "\n" + \ + "Extra long commit message for testing purposes..\n" + \ + "This should show as much longer in logs" + + results = GitObject.GitObject.parse_commit_object(contents) + + assert "tree" in results + assert results["tree"] == '7a2808c8a1d6b89d2072260593fe6341b6be0519' + assert "parent" in results + assert results["parent"] == 'e0b862c7f277a48b69d41609f30cd3f263821e00' + assert "short_msg" in results + assert results["short_msg"] == 'Adding output of blob object' + assert "long_msg" in results + assert results["long_msg"] == 'Extra long commit message for testing purposes..\nThis should show as much longer in logs' + + + # More complicated commit + 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" + + results = GitObject.GitObject.parse_commit_object(contents) + + assert "tree" in results + assert results["tree"] == "025b46d991b8602e229fa477dbc50a98ee050dcf" + assert "parent" in results + assert len(results["parent"]) == 2 + for p,expt in zip (results["parent"], ["a33e731eb4d600b08dcd34fea6fe45cecc7958a0", "f4f16ee537a618502fac0f6a0db822ddd3b45b12"]): + assert p == expt + assert "gpgsig" in results + assert results["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 results + assert results["short_msg"] == "Merge pull request #1 from kevindugan/travis-ci" + assert "long_msg" in results + assert results["long_msg"] == "Adding ci script" + + + From df1c9e611fd1c33b2e99c2c46a07bf9fb1876b7c Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 22:15:19 -0400 Subject: [PATCH 06/18] Adding commit serializer --- VersionControl/GitObject.py | 23 ++++++++++++ VersionControl/tests/test_git_object.py | 47 +++++++++++++------------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 5fcbe7f..5516b09 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -113,4 +113,27 @@ def parse_commit_object(data): else: result["long_msg"] += '\n' + item.strip() + 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"] + if "long_msg" in data: + result += "\n\n" + data["long_msg"] + return result \ No newline at end of file diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index b932307..9eea383 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -53,17 +53,19 @@ def test_parse_commit_object(): "Extra long commit message for testing purposes..\n" + \ "This should show as much longer in logs" - results = GitObject.GitObject.parse_commit_object(contents) + result = GitObject.GitObject.parse_commit_object(contents) - assert "tree" in results - assert results["tree"] == '7a2808c8a1d6b89d2072260593fe6341b6be0519' - assert "parent" in results - assert results["parent"] == 'e0b862c7f277a48b69d41609f30cd3f263821e00' - assert "short_msg" in results - assert results["short_msg"] == 'Adding output of blob object' - assert "long_msg" in results - assert results["long_msg"] == 'Extra long commit message for testing purposes..\nThis should show as much longer in logs' + assert "tree" in result + assert result["tree"] == '7a2808c8a1d6b89d2072260593fe6341b6be0519' + assert "parent" in result + assert result["parent"] == 'e0b862c7f277a48b69d41609f30cd3f263821e00' + assert "short_msg" in result + assert result["short_msg"] == 'Adding output of blob object' + assert "long_msg" in result + assert result["long_msg"] == 'Extra long commit message for testing purposes..\nThis should show as much longer in logs' + serial_result = GitObject.GitObject.serialize_commit_object(result) + assert serial_result == contents # More complicated commit contents = "tree 025b46d991b8602e229fa477dbc50a98ee050dcf\n" + \ @@ -87,16 +89,16 @@ def test_parse_commit_object(): "\n" + \ "Adding ci script" - results = GitObject.GitObject.parse_commit_object(contents) + result = GitObject.GitObject.parse_commit_object(contents) - assert "tree" in results - assert results["tree"] == "025b46d991b8602e229fa477dbc50a98ee050dcf" - assert "parent" in results - assert len(results["parent"]) == 2 - for p,expt in zip (results["parent"], ["a33e731eb4d600b08dcd34fea6fe45cecc7958a0", "f4f16ee537a618502fac0f6a0db822ddd3b45b12"]): + 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 results - assert results["gpgsig"] == "-----BEGIN PGP SIGNATURE-----\n" + \ + assert "gpgsig" in result + assert result["gpgsig"] == "-----BEGIN PGP SIGNATURE-----\n" + \ "\n" + \ "wsBcBAABCAAQBQJc/Ac+CRBK7hj4Ov3rIwAAdHIIAJVYNA8ZCS8Gl/ckHB+SSwIt\n" + \ "Uu78eWzEcR/kg229iBJWhUix0M3q0ku40aMwB5YjxVvRWNUE9OYiXqqYMvRHWopO\n" + \ @@ -108,10 +110,11 @@ def test_parse_commit_object(): "-----END PGP SIGNATURE-----\n" - assert "short_msg" in results - assert results["short_msg"] == "Merge pull request #1 from kevindugan/travis-ci" - assert "long_msg" in results - assert results["long_msg"] == "Adding ci script" - + 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 From 089cb5677f7d163a229e5f0d730bdacb759067f6 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sat, 8 Jun 2019 23:37:44 -0400 Subject: [PATCH 07/18] Adding concrete blob object --- VersionControl/BlobObject.py | 13 +++++++++++++ VersionControl/GitObject.py | 26 ++++++++++++++++--------- VersionControl/GitObjectFactory.py | 10 ++++++++++ VersionControl/WyagLib.py | 17 +++++++++++----- VersionControl/tests/test_git_object.py | 24 +++++++++-------------- 5 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 VersionControl/BlobObject.py create mode 100644 VersionControl/GitObjectFactory.py diff --git a/VersionControl/BlobObject.py b/VersionControl/BlobObject.py new file mode 100644 index 0000000..e5bda41 --- /dev/null +++ b/VersionControl/BlobObject.py @@ -0,0 +1,13 @@ +from VersionControl.GitObject import GitObject +import os + +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/GitObject.py b/VersionControl/GitObject.py index 5516b09..8448f92 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -3,21 +3,28 @@ import zlib import re import collections +from VersionControl import GitObjectFactory class GitObject(object): - def __init__(self, repo): + def __init__(self, repo, data=None): self.repository = repo + if data is not None: + self.deserializeData(data) + + def getObjectType(self): + raise NotImplementedError() - def create_object(self, path, actually_write=False): - if not os.path.isabs(path): - path = os.path.abspath(path) + def serializeData(self): + raise NotImplementedError() - contents = "" - with open(path) as f: - contents = f.read() + def deserializeData(self, path): + raise NotImplementedError() + + def create_object(self, actually_write=False): - header = "blob " + str(len(contents)) + '\0' + contents = self.serializeData() + header = self.getObjectType() + " " + str(len(contents)) + '\0' storage = (header + contents).encode() sha = hashlib.sha1(storage).hexdigest() @@ -35,6 +42,7 @@ 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()) @@ -51,7 +59,7 @@ def read_object(self, sha_hash): assert obj_format == 'blob' - return contents + return GitObjectFactory.GitObjectFactory.factory(obj_format, self.repository, contents) @staticmethod def parse_commit_object(data): diff --git a/VersionControl/GitObjectFactory.py b/VersionControl/GitObjectFactory.py new file mode 100644 index 0000000..93ecac1 --- /dev/null +++ b/VersionControl/GitObjectFactory.py @@ -0,0 +1,10 @@ +from VersionControl import BlobObject + +class GitObjectFactory(object): + + @staticmethod + def factory(type, repository, data=None): + if type == "blob": + return BlobObject.BlobObject(repository, data) + else: + raise RuntimeError("Unknown Object Type: "+str(type)) \ No newline at end of file diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index 17544ea..25842c7 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -1,7 +1,7 @@ import argparse import sys, os from VersionControl import GitRepository -from VersionControl import GitObject +from VersionControl import GitObjectFactory class wyag(object): @@ -19,9 +19,11 @@ def processCLI(self, arg_list=None): 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'], 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'], default='blob') if arg_list is not None and len(arg_list) < 1: parser.print_help() @@ -38,11 +40,16 @@ def run(self, arg_list=None): if cli_args["command"] == "init": repo = GitRepository.GitRepository(cli_args['path']) repo.initializeGitDir() + elif cli_args["command"] == "hash-object": repo = GitRepository.GitRepository.find_repo(cli_args['path']) - obj = GitObject.GitObject(repo) - print(obj.create_object(cli_args['path'], cli_args['write_obj'])) + 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 = GitObject.GitObject(repo) - print(obj.read_object(cli_args['hash']), end='') \ No newline at end of file + obj = GitObjectFactory.GitObjectFactory.factory(cli_args["object_type"], repo) + print(obj.read_object(cli_args['hash']).serializeData(), end='') \ No newline at end of file diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index 9eea383..8bcf148 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -1,5 +1,5 @@ import pytest -from VersionControl import GitRepository, GitObject +from VersionControl import GitRepository, BlobObject, GitObject import os def test_create_blob_object(tmpdir): @@ -10,16 +10,12 @@ def test_create_blob_object(tmpdir): contents = 'blah\nblahblah\nblah blah blah\n\nblah\n' expected_hash = '5e9548362714d1cef162e97a23a7a42f7311e54d' - filePath = os.path.join(tmpdir, "myTest", "test.txt") - with open(filePath, "w") as f: - f.write(contents) - - obj = GitObject.GitObject(repo) - sha = obj.create_object(filePath, False) + obj = BlobObject.BlobObject(repo, contents) + sha = obj.create_object(False) assert sha == expected_hash - sha = obj.create_object(filePath, True) + 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): @@ -30,14 +26,10 @@ def test_read_blob_object(tmpdir): contents = 'blah\nblahblah\nblah blah blah\n\nblah\n' expected_hash = '5e9548362714d1cef162e97a23a7a42f7311e54d' - filePath = os.path.join(tmpdir, "myTest", "test.txt") - with open(filePath, "w") as f: - f.write(contents) - - obj = GitObject.GitObject(repo) - sha = obj.create_object(filePath, True) + obj = BlobObject.BlobObject(repo, contents) + sha = obj.create_object(True) - result = obj.read_object(sha) + result = obj.read_object(sha).serializeData() assert result == contents def test_parse_commit_object(): @@ -118,3 +110,5 @@ def test_parse_commit_object(): serial_result = GitObject.GitObject.serialize_commit_object(result) assert serial_result == contents +def test_log(): + pass \ No newline at end of file From 09115d0f5b3d3d95e31de3cf2e1259b205cb7e06 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 00:03:04 -0400 Subject: [PATCH 08/18] Adding commit object --- VersionControl/BlobObject.py | 1 - VersionControl/CommitObject.py | 12 +++ VersionControl/GitObject.py | 3 +- VersionControl/GitObjectFactory.py | 4 +- VersionControl/WyagLib.py | 4 +- VersionControl/tests/test_git_object.py | 103 +++++++++++++++++------- VersionControl/tests/test_wyag.py | 52 ++++++++++++ 7 files changed, 142 insertions(+), 37 deletions(-) create mode 100644 VersionControl/CommitObject.py diff --git a/VersionControl/BlobObject.py b/VersionControl/BlobObject.py index e5bda41..0fb6ef0 100644 --- a/VersionControl/BlobObject.py +++ b/VersionControl/BlobObject.py @@ -1,5 +1,4 @@ from VersionControl.GitObject import GitObject -import os class BlobObject(GitObject): diff --git a/VersionControl/CommitObject.py b/VersionControl/CommitObject.py new file mode 100644 index 0000000..055b5f6 --- /dev/null +++ b/VersionControl/CommitObject.py @@ -0,0 +1,12 @@ +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) \ No newline at end of file diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 8448f92..73fc8fd 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -48,6 +48,7 @@ def read_object(self, sha_hash): 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')) @@ -57,8 +58,6 @@ def read_object(self, sha_hash): if len(contents) != obj_size: raise RuntimeError("Malformed Object {0}: bad length".format(sha_hash)) - assert obj_format == 'blob' - return GitObjectFactory.GitObjectFactory.factory(obj_format, self.repository, contents) @staticmethod diff --git a/VersionControl/GitObjectFactory.py b/VersionControl/GitObjectFactory.py index 93ecac1..222f972 100644 --- a/VersionControl/GitObjectFactory.py +++ b/VersionControl/GitObjectFactory.py @@ -1,4 +1,4 @@ -from VersionControl import BlobObject +from VersionControl import BlobObject, CommitObject class GitObjectFactory(object): @@ -6,5 +6,7 @@ class GitObjectFactory(object): 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/WyagLib.py b/VersionControl/WyagLib.py index 25842c7..3e1e194 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -19,11 +19,11 @@ def processCLI(self, arg_list=None): 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'], default='blob') + 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'], default='blob') + cat_file_parser.add_argument('-t', help="Type of Object", dest="object_type", choices=['blob', 'commit'], default='blob') if arg_list is not None and len(arg_list) < 1: parser.print_help() diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index 8bcf148..930f0cd 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -1,5 +1,5 @@ import pytest -from VersionControl import GitRepository, BlobObject, GitObject +from VersionControl import GitRepository, BlobObject, GitObject, CommitObject import os def test_create_blob_object(tmpdir): @@ -35,15 +35,7 @@ def test_read_blob_object(tmpdir): def test_parse_commit_object(): # Standard Commit - contents = "tree 7a2808c8a1d6b89d2072260593fe6341b6be0519\n" + \ - "parent e0b862c7f277a48b69d41609f30cd3f263821e00\n" + \ - "author Kevin J. Dugan 1560037321 -0400\n" + \ - "committer Kevin J. Dugan 1560040021 -0400\n" + \ - "\n" + \ - "Adding output of blob object\n" + \ - "\n" + \ - "Extra long commit message for testing purposes..\n" + \ - "This should show as much longer in logs" + contents = get_simple_commit_data() result = GitObject.GitObject.parse_commit_object(contents) @@ -60,26 +52,7 @@ def test_parse_commit_object(): assert serial_result == contents # More complicated commit - 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" + contents = get_complex_commit_data() result = GitObject.GitObject.parse_commit_object(contents) @@ -110,5 +83,73 @@ def test_parse_commit_object(): 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 = get_complex_commit_data() + expected_hash = '0b2076b2607785d7aa94eb7fd63689f679967c04' + + 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 = get_complex_commit_data() + expected_hash = '0b2076b2607785d7aa94eb7fd63689f679967c04' + + obj = CommitObject.CommitObject(repo, contents) + sha = obj.create_object(True) + + result = obj.read_object(sha).serializeData() + assert result == contents + + + def test_log(): - pass \ No newline at end of file + pass + + +############################################################################### +# Data Setup +############################################################################### +def get_simple_commit_data(): + return "tree 7a2808c8a1d6b89d2072260593fe6341b6be0519\n" + \ + "parent e0b862c7f277a48b69d41609f30cd3f263821e00\n" + \ + "author Kevin J. Dugan 1560037321 -0400\n" + \ + "committer Kevin J. Dugan 1560040021 -0400\n" + \ + "\n" + \ + "Adding output of blob object\n" + \ + "\n" + \ + "Extra long commit message for testing purposes..\n" + \ + "This should show as much longer in logs" + +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" \ No newline at end of file diff --git a/VersionControl/tests/test_wyag.py b/VersionControl/tests/test_wyag.py index cb321a4..b21fff9 100644 --- a/VersionControl/tests/test_wyag.py +++ b/VersionControl/tests/test_wyag.py @@ -43,3 +43,55 @@ def test_blob_objects(tmpdir, capsys): 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 From 4b3aa983006e60c902c50ed15dca8ebc3c5feb03 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 10:19:33 -0400 Subject: [PATCH 09/18] Setting up log history data --- VersionControl/GitObject.py | 9 +- VersionControl/tests/test_git_object.py | 108 +++++++++++++++++++----- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 73fc8fd..6def84a 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -120,6 +120,9 @@ def parse_commit_object(data): else: result["long_msg"] += '\n' + item.strip() + # last_key = next(reversed(result)) + # result[last_key] += '\n' + return result @staticmethod @@ -139,8 +142,10 @@ def serialize_commit_object(data): result += key + " " + v.replace("\n", "\n ") + "\n" if "short_msg" in data: - result += "\n" + data["short_msg"] + result += "\n" + data["short_msg"] + '\n' if "long_msg" in data: - result += "\n\n" + data["long_msg"] + result += "\n" + data["long_msg"] + + # result += '\n' return result \ No newline at end of file diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index 930f0cd..0d56e83 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -35,24 +35,23 @@ def test_read_blob_object(tmpdir): def test_parse_commit_object(): # Standard Commit - contents = get_simple_commit_data() + contents, expected_hash = get_simple_commit_data() result = GitObject.GitObject.parse_commit_object(contents) assert "tree" in result - assert result["tree"] == '7a2808c8a1d6b89d2072260593fe6341b6be0519' + assert result["tree"] == 'aa2fd36eabc0cdb0c629c3d164d81dc31c2aa8fb' assert "parent" in result - assert result["parent"] == 'e0b862c7f277a48b69d41609f30cd3f263821e00' + assert result["parent"] == '6d2f5894d033a5c19cd8d3a41ab1626ab272ed51' assert "short_msg" in result - assert result["short_msg"] == 'Adding output of blob object' - assert "long_msg" in result - assert result["long_msg"] == 'Extra long commit message for testing purposes..\nThis should show as much longer in logs' + 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 = get_complex_commit_data() + contents, expected_hash = get_complex_commit_data() result = GitObject.GitObject.parse_commit_object(contents) @@ -87,8 +86,7 @@ def test_create_commit_object(tmpdir): repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) repo.initializeGitDir() - contents = get_complex_commit_data() - expected_hash = '0b2076b2607785d7aa94eb7fd63689f679967c04' + contents, expected_hash = get_complex_commit_data() obj = CommitObject.CommitObject(repo, contents) sha = obj.create_object(False) @@ -103,8 +101,7 @@ def test_read_commit_object(tmpdir): repo.initializeGitDir() # Create File - contents = get_complex_commit_data() - expected_hash = '0b2076b2607785d7aa94eb7fd63689f679967c04' + contents, expected_hash = get_complex_commit_data() obj = CommitObject.CommitObject(repo, contents) sha = obj.create_object(True) @@ -112,25 +109,32 @@ def test_read_commit_object(tmpdir): result = obj.read_object(sha).serializeData() assert result == contents +def test_log(tmpdir): + repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) + repo.initializeGitDir() + + historyData = git_commit_history_data() + for commit in historyData: + # filePath = os.path.join(tmpdir, "myTest", ".git", "objects", commit[1][0:2], commit[1][2:]) + # os.makedirs(os.path.dirname(filePath)) + print(repo.gitDir) + obj = CommitObject.CommitObject(repo, commit[0]) + sha = obj.create_object(True) + assert sha == commit[1] -def test_log(): - pass ############################################################################### # Data Setup ############################################################################### def get_simple_commit_data(): - return "tree 7a2808c8a1d6b89d2072260593fe6341b6be0519\n" + \ - "parent e0b862c7f277a48b69d41609f30cd3f263821e00\n" + \ - "author Kevin J. Dugan 1560037321 -0400\n" + \ - "committer Kevin J. Dugan 1560040021 -0400\n" + \ - "\n" + \ - "Adding output of blob object\n" + \ + return "tree aa2fd36eabc0cdb0c629c3d164d81dc31c2aa8fb\n" + \ + "parent 6d2f5894d033a5c19cd8d3a41ab1626ab272ed51\n" + \ + "author Kevin J. Dugan 1559970810 -0400\n" + \ + "committer Kevin J. Dugan 1559970810 -0400\n" + \ "\n" + \ - "Extra long commit message for testing purposes..\n" + \ - "This should show as much longer in logs" + "Adding readme\n", "a62df6acda73871958f89e0346c97284d1a14f23" def get_complex_commit_data(): return "tree 025b46d991b8602e229fa477dbc50a98ee050dcf\n" + \ @@ -152,4 +156,64 @@ def get_complex_commit_data(): "\n" + \ "Merge pull request #1 from kevindugan/travis-ci\n" + \ "\n" + \ - "Adding ci script" \ No newline at end of file + "Adding ci script", "0b2076b2607785d7aa94eb7fd63689f679967c04" + +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 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 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 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 From 1a9c44b6439edf5ef0a2c76fdc8378e2fce51422 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 20:14:00 -0400 Subject: [PATCH 10/18] Adding repository functionality --- VersionControl/GitRepository.py | 69 ++++++++++++++++++++++++- VersionControl/tests/test_Repository.py | 57 +++++++++++++++++++- 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/VersionControl/GitRepository.py b/VersionControl/GitRepository.py index 7092f22..c02902f 100644 --- a/VersionControl/GitRepository.py +++ b/VersionControl/GitRepository.py @@ -6,6 +6,7 @@ class GitRepository(object): def __init__(self, path): self.workTree = os.path.abspath(path) self.gitDir = os.path.join(self.workTree, ".git") + self.initialized = False def __eq__(self, other): return self.gitDir == other.gitDir @@ -31,6 +32,8 @@ def initializeGitDir(self): config = self.getDefaultConfig() config.write(f) + self.initialized = True + def getDefaultConfig(self): result = configparser.ConfigParser() @@ -41,6 +44,42 @@ def getDefaultConfig(self): return result + def getHeadPath(self): + if not self.initialized: + 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 self.initialized: + 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 self.initialized: + raise RuntimeError("Uninitialized repository") + + headPath = self.getHeadPath() + + sha = "" + with open(os.path.join(self.gitDir, headPath)) as f: + sha = f.read().strip() + + return sha + + def getLowestCommonAncestor(self, commits, commit_stack=[]): + assert len(commits) > 0 + + + @staticmethod def find_repo(path): path = os.path.abspath(path) @@ -55,4 +94,32 @@ def find_repo(path): print("Fatal: Not a git repository") sys.exit(1) - return GitRepository.find_repo(parent) \ No newline at end of file + return GitRepository.find_repo(parent) + +class CommitQueue(object): + + def __init__(self): + self.queue = [] + + def size(self): + return len(self.queue) + + 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/tests/test_Repository.py b/VersionControl/tests/test_Repository.py index 3fe039e..dd1565d 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,57 @@ 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 + +def test_commit_queue(): + + queue = GitRepository.CommitQueue() + assert queue.size() == 0 + + 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" \ No newline at end of file From f250f6db799e7820044f1fd881c9388c861ecbf6 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 21:02:19 -0400 Subject: [PATCH 11/18] Adding functionality for log --- VersionControl/GitObject.py | 94 +++++++++++++++++++++++- VersionControl/GitRepository.py | 35 +-------- VersionControl/tests/test_Repository.py | 28 +------- VersionControl/tests/test_git_object.py | 95 +++++++++++++++++++++---- 4 files changed, 178 insertions(+), 74 deletions(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 6def84a..8b447e8 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -148,4 +148,96 @@ def serialize_commit_object(data): # result += '\n' - return result \ No newline at end of file + return result + + def fillCommitQueue(self, sha, queue): + queue.add(sha) + + contents = self.read_object(sha) + assert contents.getObjectType() == "commit" + if "parent" not in contents.commitData.keys(): + return + + parent = "" + if type(contents.commitData["parent"]) == list: + parent = contents.commitData["parent"][0] + else: + parent = contents.commitData["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=[]): + + if sha in result: + return result + result.append(sha) + + contents = self.read_object(sha) + assert contents.getObjectType() == "commit" + + if "parent" not in contents.commitData.keys(): + return result + + parents = contents.commitData["parent"] + if type(parents) != list: + parents = [ parents ] + + for p in parents: + print ("Commit: " + sha) + result = self.getLog(p, result) + + return result + + + + +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/GitRepository.py b/VersionControl/GitRepository.py index c02902f..16d7ef9 100644 --- a/VersionControl/GitRepository.py +++ b/VersionControl/GitRepository.py @@ -75,11 +75,6 @@ def getHeadCommit(self): return sha - def getLowestCommonAncestor(self, commits, commit_stack=[]): - assert len(commits) > 0 - - - @staticmethod def find_repo(path): path = os.path.abspath(path) @@ -94,32 +89,4 @@ def find_repo(path): print("Fatal: Not a git repository") sys.exit(1) - return GitRepository.find_repo(parent) - -class CommitQueue(object): - - def __init__(self): - self.queue = [] - - def size(self): - return len(self.queue) - - 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 + return GitRepository.find_repo(parent) \ No newline at end of file diff --git a/VersionControl/tests/test_Repository.py b/VersionControl/tests/test_Repository.py index dd1565d..458fc56 100644 --- a/VersionControl/tests/test_Repository.py +++ b/VersionControl/tests/test_Repository.py @@ -116,30 +116,4 @@ def test_set_head(tmpdir): with open(headPath) as f: assert sha == f.read().strip() - assert repo.getHeadCommit() == sha - -def test_commit_queue(): - - queue = GitRepository.CommitQueue() - assert queue.size() == 0 - - 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" \ No newline at end of file + assert repo.getHeadCommit() == sha \ No newline at end of file diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index 0d56e83..0aef508 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -108,21 +108,92 @@ def test_read_commit_object(tmpdir): result = obj.read_object(sha).serializeData() assert result == contents - + +def test_commit_queue(): + + queue = GitObject.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 = GitObject.CommitQueue() + obj = CommitObject.CommitObject(repo) + obj.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] + + obj = GitObject.GitObject(repo) + result = obj.getLowestCommonAncestor(commits=[sha1, sha2]) + assert result == base + + def test_log(tmpdir): repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) repo.initializeGitDir() historyData = git_commit_history_data() for commit in historyData: - # filePath = os.path.join(tmpdir, "myTest", ".git", "objects", commit[1][0:2], commit[1][2:]) - # os.makedirs(os.path.dirname(filePath)) - print(repo.gitDir) obj = CommitObject.CommitObject(repo, commit[0]) sha = obj.create_object(True) assert sha == commit[1] + head = historyData[-1][1] + # print(head) + obj = CommitObject.CommitObject(repo) + shaList = obj.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] + ############################################################################### @@ -175,14 +246,6 @@ def git_commit_history_data(): "Added new line\n", "30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098"]) - 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 24cc7c0ead06780c12ae9ae79fc7fb69c0f054ee\n" + \ "parent 30f3cf9a5f3b33b6090f469c6f8fd8ced8aad098\n" + \ "author Kevin J. Dugan 1560084096 -0400\n" + \ @@ -199,6 +262,14 @@ def git_commit_history_data(): "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" + \ From 123ffeb1b31d9865a56875a5528fb58428ad779c Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 21:24:27 -0400 Subject: [PATCH 12/18] Adding log function --- VersionControl/GitObject.py | 12 ++++++++++-- VersionControl/tests/test_git_object.py | 9 ++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 8b447e8..70dc3bf 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -186,7 +186,7 @@ def getLowestCommonAncestor(self, commits): return queue.top() - def getLog(self, sha, result=[]): + def getLog(self, sha, result=[], LCA=None): if sha in result: return result @@ -202,9 +202,17 @@ def getLog(self, sha, result=[]): if type(parents) != list: parents = [ parents ] + if len(parents) > 1: + LCA = self.getLowestCommonAncestor(parents) + for p in parents: + if p == LCA: + return result print ("Commit: " + sha) - result = self.getLog(p, result) + result = self.getLog(p, result, LCA) + + if not LCA is None: + result = self.getLog(LCA, result) return result diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index 0aef508..e63f9c0 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -170,6 +170,9 @@ def test_base_commit(tmpdir): result = obj.getLowestCommonAncestor(commits=[sha1, sha2]) assert result == base + result = obj.getLowestCommonAncestor(commits=[sha1]) + assert result == sha1 + def test_log(tmpdir): repo = GitRepository.GitRepository(os.path.join(tmpdir, "myTest")) @@ -190,9 +193,9 @@ def test_log(tmpdir): 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] + assert len(shaList) == len(historyData) + for result, expected in zip(shaList,reversed(historyData)): + assert result == expected[1] From 87617fa87da47afff43a379f7cb9975a7724fbd3 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 22:19:11 -0400 Subject: [PATCH 13/18] Adding Log command --- VersionControl/GitObject.py | 28 ++++++++++++++++++++++++- VersionControl/GitRepository.py | 7 +++---- VersionControl/WyagLib.py | 14 ++++++++++++- VersionControl/tests/test_git_object.py | 2 ++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 70dc3bf..16d70e0 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -4,6 +4,7 @@ import re import collections from VersionControl import GitObjectFactory +from datetime import datetime class GitObject(object): @@ -208,7 +209,6 @@ def getLog(self, sha, result=[], LCA=None): for p in parents: if p == LCA: return result - print ("Commit: " + sha) result = self.getLog(p, result, LCA) if not LCA is None: @@ -216,6 +216,32 @@ def getLog(self, sha, result=[], LCA=None): return result + def printLog(self, sha_history): + message = "" + for commit in sha_history: + contents = self.read_object(commit) + message += "commit " + commit + "\n" + if "parent" in contents.commitData: + if type(contents.commitData["parent"]) == list: + message += "Merge:" + for p in contents.commitData["parent"]: + message += " " + p[:7] + message += "\n" + header = contents.commitData["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.fromtimestamp(date) + message += "Date: " + date.strftime("%a %b %-d %H:%M:%S %Y") + " " + zone + "\n" + message += "\n" + message += " " + contents.commitData["short_msg"] + "\n" + message += "\n" + + # Remove last new line + print(message.rstrip('\n')) diff --git a/VersionControl/GitRepository.py b/VersionControl/GitRepository.py index 16d7ef9..f3a00f6 100644 --- a/VersionControl/GitRepository.py +++ b/VersionControl/GitRepository.py @@ -6,7 +6,6 @@ class GitRepository(object): def __init__(self, path): self.workTree = os.path.abspath(path) self.gitDir = os.path.join(self.workTree, ".git") - self.initialized = False def __eq__(self, other): return self.gitDir == other.gitDir @@ -45,7 +44,7 @@ def getDefaultConfig(self): return result def getHeadPath(self): - if not self.initialized: + if not os.path.isdir(self.gitDir): raise RuntimeError("Uninitialized repository") headPath = "" @@ -55,7 +54,7 @@ def getHeadPath(self): return headPath def setHeadCommit(self, sha): - if not self.initialized: + if not os.path.isdir(self.gitDir): raise RuntimeError("Uninitialized repository") headPath = self.getHeadPath() @@ -64,7 +63,7 @@ def setHeadCommit(self, sha): f.write(sha) def getHeadCommit(self): - if not self.initialized: + if not os.path.isdir(self.gitDir): raise RuntimeError("Uninitialized repository") headPath = self.getHeadPath() diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index 3e1e194..77185d7 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -25,6 +25,9 @@ def processCLI(self, arg_list=None): 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') + if arg_list is not None and len(arg_list) < 1: parser.print_help() sys.exit(1) @@ -52,4 +55,13 @@ def run(self, arg_list=None): 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='') \ No newline at end of file + print(obj.read_object(cli_args['hash']).serializeData(), end='') + + elif cli_args["command"] == "log": + repo = GitRepository.GitRepository.find_repo(os.getcwd()) + obj = GitObjectFactory.GitObjectFactory.factory("commit", repo) + sha = cli_args["hash"] + if sha == "head": + sha = repo.getHeadCommit() + log = obj.getLog(sha) + obj.printLog(log) diff --git a/VersionControl/tests/test_git_object.py b/VersionControl/tests/test_git_object.py index e63f9c0..bd1b1aa 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -196,6 +196,8 @@ def test_log(tmpdir): assert len(shaList) == len(historyData) for result, expected in zip(shaList,reversed(historyData)): assert result == expected[1] + + obj.printLog(shaList) From 6e8c22848eb860f5eb72da16554360f823860b3d Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 22:25:27 -0400 Subject: [PATCH 14/18] Adding Colored Output --- VersionControl/GitObject.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 16d70e0..87428e8 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -5,6 +5,7 @@ import collections from VersionControl import GitObjectFactory from datetime import datetime +from termcolor import colored class GitObject(object): @@ -220,7 +221,7 @@ def printLog(self, sha_history): message = "" for commit in sha_history: contents = self.read_object(commit) - message += "commit " + commit + "\n" + message += colored("commit " + commit + "\n", 'yellow') if "parent" in contents.commitData: if type(contents.commitData["parent"]) == list: message += "Merge:" From 1b6a33289844312672c8b2e948ba8fd50b53bc95 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 23:33:19 -0400 Subject: [PATCH 15/18] Adding log command test --- VersionControl/GitObject.py | 7 +- VersionControl/WyagLib.py | 3 +- VersionControl/tests/test_wyag.py | 133 ++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 3 deletions(-) diff --git a/VersionControl/GitObject.py b/VersionControl/GitObject.py index 87428e8..5042982 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -217,11 +217,14 @@ def getLog(self, sha, result=[], LCA=None): return result - def printLog(self, sha_history): + def printLog(self, sha_history, colored_output=True): message = "" for commit in sha_history: contents = self.read_object(commit) - message += colored("commit " + commit + "\n", 'yellow') + if colored_output: + message += colored("commit " + commit + "\n", 'yellow') + else: + message += "commit " + commit + "\n" if "parent" in contents.commitData: if type(contents.commitData["parent"]) == list: message += "Merge:" diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index 77185d7..f876dbf 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -27,6 +27,7 @@ def processCLI(self, arg_list=None): 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() @@ -64,4 +65,4 @@ def run(self, arg_list=None): if sha == "head": sha = repo.getHeadCommit() log = obj.getLog(sha) - obj.printLog(log) + obj.printLog(log, colored_output=cli_args["color_out"]) diff --git a/VersionControl/tests/test_wyag.py b/VersionControl/tests/test_wyag.py index b21fff9..21ba94a 100644 --- a/VersionControl/tests/test_wyag.py +++ b/VersionControl/tests/test_wyag.py @@ -95,3 +95,136 @@ def test_commit_objects(tmpdir, capsys): 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 08: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 08: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 08: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 08: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 08: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 08: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 08:39:40 2019 -0400\n" + message += "\n" + message += " Initial Commit" + return message \ No newline at end of file From f4172ef618ab173d9d554e26dcdb5a343d61e4ac Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Sun, 9 Jun 2019 23:59:03 -0400 Subject: [PATCH 16/18] Refactored logging into class --- VersionControl/CommitObject.py | 5 +- VersionControl/GitHistory.py | 145 ++++++++++++++++++++ VersionControl/GitObject.py | 135 +------------------ VersionControl/WyagLib.py | 7 +- VersionControl/tests/test_git_history.py | 165 +++++++++++++++++++++++ VersionControl/tests/test_git_object.py | 147 -------------------- 6 files changed, 322 insertions(+), 282 deletions(-) create mode 100644 VersionControl/GitHistory.py create mode 100644 VersionControl/tests/test_git_history.py diff --git a/VersionControl/CommitObject.py b/VersionControl/CommitObject.py index 055b5f6..12836fe 100644 --- a/VersionControl/CommitObject.py +++ b/VersionControl/CommitObject.py @@ -9,4 +9,7 @@ def serializeData(self): return GitObject.serialize_commit_object(self.commitData) def deserializeData(self, data): - self.commitData = GitObject.parse_commit_object(data) \ No newline at end of file + 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..712d263 --- /dev/null +++ b/VersionControl/GitHistory.py @@ -0,0 +1,145 @@ +from VersionControl import GitObjectFactory +from datetime import datetime +from termcolor import colored +import re + +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 += colored("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.fromtimestamp(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 index 5042982..b5960dd 100644 --- a/VersionControl/GitObject.py +++ b/VersionControl/GitObject.py @@ -4,8 +4,6 @@ import re import collections from VersionControl import GitObjectFactory -from datetime import datetime -from termcolor import colored class GitObject(object): @@ -23,6 +21,9 @@ def serializeData(self): def deserializeData(self, path): raise NotImplementedError() + def data(self): + raise NotImplementedError() + def create_object(self, actually_write=False): contents = self.serializeData() @@ -150,132 +151,4 @@ def serialize_commit_object(data): # result += '\n' - return result - - def fillCommitQueue(self, sha, queue): - queue.add(sha) - - contents = self.read_object(sha) - assert contents.getObjectType() == "commit" - if "parent" not in contents.commitData.keys(): - return - - parent = "" - if type(contents.commitData["parent"]) == list: - parent = contents.commitData["parent"][0] - else: - parent = contents.commitData["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) - - contents = self.read_object(sha) - assert contents.getObjectType() == "commit" - - if "parent" not in contents.commitData.keys(): - return result - - parents = contents.commitData["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: - contents = self.read_object(commit) - if colored_output: - message += colored("commit " + commit + "\n", 'yellow') - else: - message += "commit " + commit + "\n" - if "parent" in contents.commitData: - if type(contents.commitData["parent"]) == list: - message += "Merge:" - for p in contents.commitData["parent"]: - message += " " + p[:7] - message += "\n" - header = contents.commitData["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.fromtimestamp(date) - message += "Date: " + date.strftime("%a %b %-d %H:%M:%S %Y") + " " + zone + "\n" - message += "\n" - message += " " + contents.commitData["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 + return result \ No newline at end of file diff --git a/VersionControl/WyagLib.py b/VersionControl/WyagLib.py index f876dbf..5ee15c1 100644 --- a/VersionControl/WyagLib.py +++ b/VersionControl/WyagLib.py @@ -2,6 +2,7 @@ import sys, os from VersionControl import GitRepository from VersionControl import GitObjectFactory +from VersionControl import GitHistory class wyag(object): @@ -60,9 +61,9 @@ def run(self, arg_list=None): elif cli_args["command"] == "log": repo = GitRepository.GitRepository.find_repo(os.getcwd()) - obj = GitObjectFactory.GitObjectFactory.factory("commit", repo) + history = GitHistory.GitHistory(repo) sha = cli_args["hash"] if sha == "head": sha = repo.getHeadCommit() - log = obj.getLog(sha) - obj.printLog(log, colored_output=cli_args["color_out"]) + log = history.getLog(sha) + history.printLog(log, colored_output=cli_args["color_out"]) 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 index bd1b1aa..47dc052 100644 --- a/VersionControl/tests/test_git_object.py +++ b/VersionControl/tests/test_git_object.py @@ -109,95 +109,7 @@ def test_read_commit_object(tmpdir): result = obj.read_object(sha).serializeData() assert result == contents -def test_commit_queue(): - queue = GitObject.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 = GitObject.CommitQueue() - obj = CommitObject.CommitObject(repo) - obj.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] - - obj = GitObject.GitObject(repo) - result = obj.getLowestCommonAncestor(commits=[sha1, sha2]) - assert result == base - - result = obj.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) - - obj = CommitObject.CommitObject(repo) - shaList = obj.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] - - obj.printLog(shaList) @@ -234,62 +146,3 @@ def get_complex_commit_data(): "\n" + \ "Adding ci script", "0b2076b2607785d7aa94eb7fd63689f679967c04" -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 From a14211e6bec080046cdf5b8da7c2de88834b508a Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Mon, 10 Jun 2019 21:10:31 -0400 Subject: [PATCH 17/18] Testing import of termcolor --- VersionControl/GitHistory.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/VersionControl/GitHistory.py b/VersionControl/GitHistory.py index 712d263..5130afe 100644 --- a/VersionControl/GitHistory.py +++ b/VersionControl/GitHistory.py @@ -1,8 +1,17 @@ from VersionControl import GitObjectFactory from datetime import datetime -from termcolor import colored +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={}): @@ -82,7 +91,7 @@ def printLog(self, sha_history, colored_output=True): obj = GitObjectFactory.GitObjectFactory.factory("commit", self.repository) contents = obj.read_object(commit).data() if colored_output: - message += colored("commit " + commit + "\n", 'yellow') + message += color_out("commit " + commit + "\n", 'yellow') else: message += "commit " + commit + "\n" if "parent" in contents.keys(): From a2f6ce98e79d5c83d5db5476d8da950264361996 Mon Sep 17 00:00:00 2001 From: "Kevin J. Dugan" Date: Mon, 10 Jun 2019 21:55:52 -0400 Subject: [PATCH 18/18] Adjusting time zones --- VersionControl/GitHistory.py | 2 +- VersionControl/tests/test_wyag.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/VersionControl/GitHistory.py b/VersionControl/GitHistory.py index 5130afe..a1eb65c 100644 --- a/VersionControl/GitHistory.py +++ b/VersionControl/GitHistory.py @@ -107,7 +107,7 @@ def printLog(self, sha_history, colored_output=True): zone = CA[1].split()[1] message += "Author: " + author + "\n" - date = datetime.fromtimestamp(date) + date = datetime.utcfromtimestamp(date) message += "Date: " + date.strftime("%a %b %-d %H:%M:%S %Y") + " " + zone + "\n" message += "\n" message += " " + contents["short_msg"] + "\n" diff --git a/VersionControl/tests/test_wyag.py b/VersionControl/tests/test_wyag.py index 21ba94a..e57fc71 100644 --- a/VersionControl/tests/test_wyag.py +++ b/VersionControl/tests/test_wyag.py @@ -187,44 +187,44 @@ def get_log_output_plain(): message = "" message += "commit 075c7e021c0d2e4a43f01a2e848daf605ed4e65f\n" message += "Author: Kevin J. Dugan \n" - message += "Date: Sun Jun 9 08:47:54 2019 -0400\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 08:46:57 2019 -0400\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 08:46:02 2019 -0400\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 08:42:04 2019 -0400\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 08:41:36 2019 -0400\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 08:40:40 2019 -0400\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 08:39:40 2019 -0400\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