diff --git a/.github/workflows/cs102.yml b/.github/workflows/cs102.yml
index 4509ac4..1abff40 100644
--- a/.github/workflows/cs102.yml
+++ b/.github/workflows/cs102.yml
@@ -7,10 +7,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - name: Set up Python 3.8.6
+ - name: Set up Python 3.10.10
uses: actions/setup-python@v2
with:
- python-version: '3.8.6'
+ python-version: '3.10.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/857l.iml b/.idea/857l.iml
new file mode 100644
index 0000000..8e5446a
--- /dev/null
+++ b/.idea/857l.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..aad1270
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..592a42b
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..1d9aaec
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/homework04/.idea/.gitignore b/homework04/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/homework04/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/homework04/.idea/homework04.iml b/homework04/.idea/homework04.iml
new file mode 100644
index 0000000..8e5446a
--- /dev/null
+++ b/homework04/.idea/homework04.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/homework04/.idea/inspectionProfiles/profiles_settings.xml b/homework04/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/homework04/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/homework04/.idea/misc.xml b/homework04/.idea/misc.xml
new file mode 100644
index 0000000..a4fea96
--- /dev/null
+++ b/homework04/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/homework04/.idea/modules.xml b/homework04/.idea/modules.xml
new file mode 100644
index 0000000..caedf72
--- /dev/null
+++ b/homework04/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/homework04/pyvcs.egg-info/PKG-INFO b/homework04/pyvcs.egg-info/PKG-INFO
new file mode 100644
index 0000000..78f5b9f
--- /dev/null
+++ b/homework04/pyvcs.egg-info/PKG-INFO
@@ -0,0 +1,13 @@
+Metadata-Version: 2.1
+Name: pyvcs
+Version: 0.1.0
+Summary: The stupid content tracker
+Home-page: https://github.com/Dementiy/pybook-assignments
+Author: Dmitrii Sorokin
+Author-email: dementiy@yandex.ru
+License: GPLv3
+Platform: UNKNOWN
+Requires-Python: >=3.6.0
+
+UNKNOWN
+
diff --git a/homework04/pyvcs.egg-info/SOURCES.txt b/homework04/pyvcs.egg-info/SOURCES.txt
new file mode 100644
index 0000000..bc5e160
--- /dev/null
+++ b/homework04/pyvcs.egg-info/SOURCES.txt
@@ -0,0 +1,15 @@
+setup.py
+pyvcs/__init__.py
+pyvcs/__main__.py
+pyvcs/cli.py
+pyvcs/index.py
+pyvcs/objects.py
+pyvcs/porcelain.py
+pyvcs/refs.py
+pyvcs/repo.py
+pyvcs/tree.py
+pyvcs.egg-info/PKG-INFO
+pyvcs.egg-info/SOURCES.txt
+pyvcs.egg-info/dependency_links.txt
+pyvcs.egg-info/entry_points.txt
+pyvcs.egg-info/top_level.txt
\ No newline at end of file
diff --git a/homework04/pyvcs.egg-info/dependency_links.txt b/homework04/pyvcs.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/homework04/pyvcs.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/homework04/pyvcs.egg-info/entry_points.txt b/homework04/pyvcs.egg-info/entry_points.txt
new file mode 100644
index 0000000..bba7a0d
--- /dev/null
+++ b/homework04/pyvcs.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+pyvcs = pyvcs.__main__:main
+
diff --git a/homework04/pyvcs.egg-info/top_level.txt b/homework04/pyvcs.egg-info/top_level.txt
new file mode 100644
index 0000000..809a5ab
--- /dev/null
+++ b/homework04/pyvcs.egg-info/top_level.txt
@@ -0,0 +1 @@
+pyvcs
diff --git a/homework04/pyvcs/__main__.py b/homework04/pyvcs/__main__.py
index 797f8e9..4d941b2 100644
--- a/homework04/pyvcs/__main__.py
+++ b/homework04/pyvcs/__main__.py
@@ -116,6 +116,7 @@ def add_write_tree_subparser(subparsers) -> None:
def add_commit_tree_subparser(subparsers) -> None:
+ # FIXME: Add author
commit_tree_subparser = subparsers.add_parser("commit-tree", help="Create a new commit object.")
commit_tree_subparser.add_argument("tree", help="An existing tree object")
commit_tree_subparser.add_argument("-p", dest="parent", help="Id of a parent commit object")
diff --git a/homework04/pyvcs/index.py b/homework04/pyvcs/index.py
index 85a5e91..658de35 100644
--- a/homework04/pyvcs/index.py
+++ b/homework04/pyvcs/index.py
@@ -25,30 +25,93 @@ class GitIndexEntry(tp.NamedTuple):
name: str
def pack(self) -> bytes:
- # PUT YOUR CODE HERE
- ...
+ # pack the data
+ return (
+ struct.pack(
+ f">LLLLLLLLLL20sH{len(self.name.encode())}s", *self[:-1], self.name.encode()
+ )
+ + b"\x00\x00\x00"
+ )
@staticmethod
def unpack(data: bytes) -> "GitIndexEntry":
- # PUT YOUR CODE HERE
- ...
+ size = struct.calcsize(">LLLLLLLLLL20sH")
+ # unpack the data
+ values = struct.unpack(f">LLLLLLLLLL20sH", data[:size])
+ # add in tuple value below
+ values += (data[size:].split(b"\x00\x00\x00")[0].decode(),)
+ return GitIndexEntry(
+ *values,
+ )
def read_index(gitdir: pathlib.Path) -> tp.List[GitIndexEntry]:
- # PUT YOUR CODE HERE
- ...
+ if not (gitdir / "index").exists():
+ return []
+
+ with (gitdir / "index").open("rb") as f:
+ data = f.read()
+ if data[:4] != b"DIRC":
+ raise Exception("Not a valid index file")
+ version, count = struct.unpack(">LL", data[4:12])
+ if version != 2:
+ raise Exception("Unsupported index version")
+ entries = []
+ offset = 12
+ for _ in range(count):
+ entry = GitIndexEntry.unpack(data[offset:])
+ entries.append(entry)
+ offset += 62 + len(entry.name.encode()) + 3
+ return entries
def write_index(gitdir: pathlib.Path, entries: tp.List[GitIndexEntry]) -> None:
- # PUT YOUR CODE HERE
- ...
+ data = b"DIRC" + struct.pack(">LL", 2, len(entries))
+ for entry in entries:
+ data += entry.pack()
+ with (gitdir / "index").open("wb") as f:
+ f.write(data)
+ f.write(hashlib.sha1(data).digest())
def ls_files(gitdir: pathlib.Path, details: bool = False) -> None:
- # PUT YOUR CODE HERE
- ...
+ entries = read_index(gitdir)
+ for entry in entries:
+ if details:
+ print(f"{entry.mode:o} {entry.sha1.hex()} 0\t{entry.name}")
+ else:
+ print(entry.name)
def update_index(gitdir: pathlib.Path, paths: tp.List[pathlib.Path], write: bool = True) -> None:
- # PUT YOUR CODE HERE
- ...
+ entries = read_index(gitdir)
+ for path in paths:
+ if not path.exists():
+ raise Exception(f"Path {path} does not exist")
+ with path.open("rb") as f:
+ data = f.read()
+ sha1 = hash_object(data, "blob", write=True)
+ for entry in entries:
+ if entry.name == str(path):
+ entries.remove(entry)
+ break
+ entries.append(
+ GitIndexEntry(
+ int(path.stat().st_ctime),
+ int(path.stat().st_ctime_ns % 1000000000),
+ int(path.stat().st_mtime),
+ int(path.stat().st_mtime_ns % 1000000000),
+ path.stat().st_dev,
+ path.stat().st_ino,
+ path.stat().st_mode,
+ path.stat().st_uid,
+ path.stat().st_gid,
+ path.stat().st_size,
+ bytes.fromhex(sha1),
+ 0,
+ str(path).replace("\\", "/"),
+ )
+ )
+ if write:
+ entries.sort(key=operator.attrgetter("name"))
+ write_index(gitdir, entries)
diff --git a/homework04/pyvcs/objects.py b/homework04/pyvcs/objects.py
index 013cc8d..360258c 100644
--- a/homework04/pyvcs/objects.py
+++ b/homework04/pyvcs/objects.py
@@ -11,40 +11,102 @@
def hash_object(data: bytes, fmt: str, write: bool = False) -> str:
- # PUT YOUR CODE HERE
- ...
+ header = f"{fmt} {len(data)}\0".encode()
+ store = header + data
+ sha = hashlib.sha1(store).hexdigest()
+ if write:
+ gitdir = repo_find()
+ path = gitdir / "objects" / sha[:2] / sha[2:]
+ path.parent.mkdir(parents=True, exist_ok=True)
+ with path.open("wb") as f:
+ f.write(zlib.compress(store))
+ return sha
def resolve_object(obj_name: str, gitdir: pathlib.Path) -> tp.List[str]:
- # PUT YOUR CODE HERE
- ...
+ if len(obj_name) < 4 or len(obj_name) > 40:
+ raise Exception(f"Not a valid object name {obj_name}")
+
+ objects = []
+
+ for i in (gitdir / "objects").iterdir():
+ if i.is_dir() and i.name == obj_name[:2]:
+ for j in i.iterdir():
+ if obj_name[2:] in j.name:
+ objects.append(i.name + j.name)
+
+ if len(objects) == 0:
+ raise Exception(f"Not a valid object name {obj_name}")
+
+ return objects
def find_object(obj_name: str, gitdir: pathlib.Path) -> str:
- # PUT YOUR CODE HERE
- ...
+ object_new = resolve_object(obj_name, gitdir)
+ if len(object_new) == 1:
+ return object_new[0]
+ else:
+ raise Exception(f"Ambiguous object name {obj_name}")
def read_object(sha: str, gitdir: pathlib.Path) -> tp.Tuple[str, bytes]:
- # PUT YOUR CODE HERE
- ...
+ object_new = find_object(sha, gitdir)
+ path = gitdir / "objects" / object_new[:2] / object_new[2:]
+ with path.open("rb") as f:
+ data = zlib.decompress(f.read())
+ return data.split(b" ", maxsplit=1)[0].decode(), data.split(b"\0", maxsplit=1)[1]
def read_tree(data: bytes) -> tp.List[tp.Tuple[int, str, str]]:
- # PUT YOUR CODE HERE
- ...
+ result = []
+ while data:
+ mode, name_sha = data.split(b" ")[:2]
+ name, sha = name_sha.split(b"\0")[:2]
+ # print(f"Mode: {mode}, Name: {name}, SHA: {sha}")
+ result.append((int(oct(int(mode, 8))[2:]), name.decode(), sha[:20].hex()))
+ data = data[len(mode) + 2 + len(name) + 20 :]
+ return result
def cat_file(obj_name: str, pretty: bool = True) -> None:
- # PUT YOUR CODE HERE
- ...
+ object_new = read_object(obj_name, repo_find())
+ if pretty:
+ if object_new[0] == "commit":
+ print(object_new[1].decode())
+ elif object_new[0] == "tree":
+ for i in read_tree(object_new[1]):
+ print(f"{str(i[0]).zfill(6)} {read_object(i[2], repo_find())[0]} {i[2]}\t{i[1]}")
+ elif object_new[0] == "blob":
+ print(object_new[1].decode())
+ else:
+ print(object_new[1].decode())
def find_tree_files(tree_sha: str, gitdir: pathlib.Path) -> tp.List[tp.Tuple[str, str]]:
- # PUT YOUR CODE HERE
- ...
+ result = []
+ for mode, name, sha in read_tree(read_object(tree_sha, gitdir)[1]):
+ if read_object(sha, gitdir)[0] == "tree":
+ tree = find_tree_files(sha, gitdir)
+ for i in tree:
+ result.append((name + "/" + i[0], i[1]))
+ else:
+ result.append((name, sha))
+ return result
def commit_parse(raw: bytes, start: int = 0, dct=None):
- # PUT YOUR CODE HERE
- ...
+ if dct is None:
+ dct = {}
+ if start == 0:
+ dct["tree"] = raw.split(b"\n", maxsplit=1)[0].decode()
+ if start == 1:
+ dct["parent"] = raw.split(b"\n", maxsplit=1)[0].decode()
+ if start == 2:
+ dct["author"] = raw.split(b"\n", maxsplit=1)[0].decode()
+ if start == 3:
+ dct["committer"] = raw.split(b"\n", maxsplit=1)[0].decode()
+ if start == 4:
+ dct["message"] = raw.split(b"\n", maxsplit=1)[0].decode()
+ if start == 5:
+ return dct
+ return commit_parse(raw.split(b"\n", maxsplit=1)[1], start + 1, dct)
diff --git a/homework04/pyvcs/porcelain.py b/homework04/pyvcs/porcelain.py
index 6f2cde2..01891c1 100644
--- a/homework04/pyvcs/porcelain.py
+++ b/homework04/pyvcs/porcelain.py
@@ -1,23 +1,51 @@
import os
import pathlib
+import shutil
import typing as tp
from pyvcs.index import read_index, update_index
-from pyvcs.objects import commit_parse, find_object, find_tree_files, read_object
+from pyvcs.objects import (
+ cat_file,
+ commit_parse,
+ find_object,
+ find_tree_files,
+ read_object,
+)
from pyvcs.refs import get_ref, is_detached, resolve_head, update_ref
from pyvcs.tree import commit_tree, write_tree
def add(gitdir: pathlib.Path, paths: tp.List[pathlib.Path]) -> None:
- # PUT YOUR CODE HERE
- ...
+ update_index(gitdir, paths)
def commit(gitdir: pathlib.Path, message: str, author: tp.Optional[str] = None) -> str:
- # PUT YOUR CODE HERE
- ...
+ return commit_tree(gitdir, write_tree(gitdir, read_index(gitdir)), message, author=author)
def checkout(gitdir: pathlib.Path, obj_name: str) -> None:
- # PUT YOUR CODE HERE
- ...
+ ref_file = gitdir / "refs" / "heads" / obj_name
+ if ref_file.exists():
+ with open(ref_file, "w") as f:
+ obj_name = f.read()
+
+ indexes = read_index(gitdir)
+ for index in indexes:
+ if pathlib.Path(index.name).is_file():
+ name = index.name.split("/")
+ if len(name) > 1:
+ shutil.rmtree(name[0])
+ else:
+ os.remove(index.name)
+
+ object_by_sha = read_object(obj_name, gitdir)
+ sha = object_by_sha[1].decode().split("\n")[0].split()[1]
+
+ for tree_obj in find_tree_files(sha, gitdir):
+ name = tree_obj[0].split("/")
+
+ if len(name) > 1:
+ pathlib.Path(name[0]).absolute().mkdir()
+
+ with open(tree_obj[0], "w") as f:
+ f.write(read_object(tree_obj[1], gitdir)[1].decode())
diff --git a/homework04/pyvcs/refs.py b/homework04/pyvcs/refs.py
index 1e45b90..7e7bdaf 100644
--- a/homework04/pyvcs/refs.py
+++ b/homework04/pyvcs/refs.py
@@ -3,30 +3,51 @@
def update_ref(gitdir: pathlib.Path, ref: tp.Union[str, pathlib.Path], new_value: str) -> None:
- # PUT YOUR CODE HERE
- ...
+ path = gitdir / ref
+ with open(path, "w") as f:
+ f.write(new_value)
def symbolic_ref(gitdir: pathlib.Path, name: str, ref: str) -> None:
- # PUT YOUR CODE HERE
- ...
+ path = gitdir / name
+ with open(path, "w") as f:
+ f.write("ref: " + ref)
-def ref_resolve(gitdir: pathlib.Path, refname: str) -> str:
- # PUT YOUR CODE HERE
- ...
+def ref_resolve(gitdir: pathlib.Path, refname: str) -> tp.Optional[str]:
+ if refname == "HEAD":
+ refname = get_ref(gitdir)
+
+ path = gitdir / refname
+ if not path.exists():
+ return None
+
+ with open(path, "r") as f:
+ ref = f.read().strip()
+ return ref
def resolve_head(gitdir: pathlib.Path) -> tp.Optional[str]:
- # PUT YOUR CODE HERE
- ...
+ return ref_resolve(gitdir, "HEAD")
def is_detached(gitdir: pathlib.Path) -> bool:
- # PUT YOUR CODE HERE
- ...
+ path = gitdir / "HEAD"
+ with open(path, "r") as f:
+ ref = f.read().strip()
+
+ if ref[:5] == "ref: ":
+ return False
+
+ return True
def get_ref(gitdir: pathlib.Path) -> str:
- # PUT YOUR CODE HERE
- ...
+ path = gitdir / "HEAD"
+ with open(path, "r") as f:
+ ref = f.read().strip()
+
+ if ref[:5] == "ref: ":
+ return ref[5:]
+
+ return ref
diff --git a/homework04/pyvcs/repo.py b/homework04/pyvcs/repo.py
index cef16a6..bd3faf9 100644
--- a/homework04/pyvcs/repo.py
+++ b/homework04/pyvcs/repo.py
@@ -4,10 +4,39 @@
def repo_find(workdir: tp.Union[str, pathlib.Path] = ".") -> pathlib.Path:
- # PUT YOUR CODE HERE
- ...
+ # Find repository directory
+ workdir = pathlib.Path(workdir)
+ if not workdir.is_dir():
+ raise NotADirectoryError(workdir)
+ gitdir = workdir / os.environ.get("GIT_DIR", ".git")
+ while not gitdir.is_dir():
+ if workdir.parent == workdir:
+ raise Exception(f"Not a git repository")
+ workdir = workdir.parent
+ gitdir = workdir / os.environ["GIT_DIR"]
+ return gitdir
def repo_create(workdir: tp.Union[str, pathlib.Path]) -> pathlib.Path:
- # PUT YOUR CODE HERE
- ...
+ # Create repository directory
+ workdir = pathlib.Path(workdir)
+ if not workdir.is_dir():
+ raise Exception(f"{workdir} is not a directory")
+ gitdir = workdir / os.environ.get("GIT_DIR", ".git")
+ if gitdir.is_dir():
+ raise FileExistsError(gitdir)
+ os.makedirs(gitdir)
+ os.makedirs(gitdir / "branches")
+ os.makedirs(gitdir / "objects")
+ os.makedirs(gitdir / "refs" / "tags")
+ os.makedirs(gitdir / "refs" / "heads")
+ with open(gitdir / "HEAD", "wt") as f:
+ f.write("ref: refs/heads/master\n")
+ with open(gitdir / "config", "wt") as f:
+ f.write(
+ "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = false\n\tlogallrefupdates = false\n"
+ )
+ with open(gitdir / "description", "wt") as f:
+ f.write("Unnamed pyvcs repository.\n")
+
+ return gitdir
diff --git a/homework04/pyvcs/tree.py b/homework04/pyvcs/tree.py
index f79b026..4ece431 100644
--- a/homework04/pyvcs/tree.py
+++ b/homework04/pyvcs/tree.py
@@ -10,8 +10,31 @@
def write_tree(gitdir: pathlib.Path, index: tp.List[GitIndexEntry], dirname: str = "") -> str:
- # PUT YOUR CODE HERE
- ...
+ tree = b""
+ for entry in index:
+ position = entry.name.rfind("/")
+ dirname = "" if position == -1 else entry.name[:position]
+ # print(entry.name)
+ # if dirname == "":
+ # tree += f"{entry.mode} {entry.name}\0{bytes.hex(entry.sha1)}"
+ # else:
+ # tree += f"{entry.mode} {entry.name[len(dirname)+1:]}\0{entry.sha1}"
+ if dirname == "":
+ tree += oct(entry.mode)[2:].encode() + b" "
+ tree += entry.name.encode() + b"\0"
+ tree += entry.sha1
+ else:
+ temp_object = b""
+ temp_object += oct(entry.mode)[2:].encode() + b" "
+ temp_object += entry.name[len(dirname) + 1 :].encode() + b"\0"
+ temp_object += entry.sha1
+ sha1 = hash_object(temp_object, "tree", True)
+
+ tree += oct(stat.S_IFDIR | 0o000)[2:].encode() + b" "
+ tree += dirname.encode() + b"\0"
+ tree += bytes.fromhex(sha1)
+
+ return hash_object(tree, "tree", True)
def commit_tree(
@@ -21,5 +44,15 @@ def commit_tree(
parent: tp.Optional[str] = None,
author: tp.Optional[str] = None,
) -> str:
- # PUT YOUR CODE HERE
- ...
+ if author is None:
+ author = f"{os.environ['GIT_AUTHOR_NAME']} <{os.environ['GIT_AUTHOR_EMAIL']}>"
+
+ timestamp = int(time.mktime(time.localtime()))
+
+ commit = f"tree {tree}\n"
+ if parent:
+ commit += f"parent {parent}\n"
+ commit += f"author {author} {timestamp} +0300\n"
+ commit += f"committer {author} {timestamp} +0300\n\n"
+ commit += f"{message}\n"
+ return hash_object(commit.encode("ascii"), "commit", True)
diff --git a/homework04/setup.py b/homework04/setup.py
index 51dc915..56f124a 100644
--- a/homework04/setup.py
+++ b/homework04/setup.py
@@ -1,6 +1,5 @@
-from setuptools import setup
-
import pyvcs
+from setuptools import setup
AUTHOR = "Dmitrii Sorokin"
AUTHOR_EMAIL = "dementiy@yandex.ru"
diff --git a/homework04/tests/test_index.py b/homework04/tests/test_index.py
index eaf5576..0905b61 100644
--- a/homework04/tests/test_index.py
+++ b/homework04/tests/test_index.py
@@ -3,9 +3,8 @@
import unittest
from unittest.mock import patch
-from pyfakefs.fake_filesystem_unittest import TestCase
-
import pyvcs
+from pyfakefs.fake_filesystem_unittest import TestCase
from pyvcs.index import GitIndexEntry, ls_files, read_index, update_index, write_index
from pyvcs.repo import repo_create
diff --git a/homework04/tests/test_objects.py b/homework04/tests/test_objects.py
index 5cbad4f..aadf5e1 100644
--- a/homework04/tests/test_objects.py
+++ b/homework04/tests/test_objects.py
@@ -5,9 +5,8 @@
import zlib
from unittest.mock import patch
-from pyfakefs.fake_filesystem_unittest import TestCase
-
import pyvcs
+from pyfakefs.fake_filesystem_unittest import TestCase
from pyvcs import index, objects, porcelain, repo, tree
diff --git a/homework04/tests/test_porcelain.py b/homework04/tests/test_porcelain.py
index a65eb51..758740f 100644
--- a/homework04/tests/test_porcelain.py
+++ b/homework04/tests/test_porcelain.py
@@ -3,9 +3,8 @@
import unittest
from unittest.mock import patch
-from pyfakefs.fake_filesystem_unittest import TestCase
-
import pyvcs
+from pyfakefs.fake_filesystem_unittest import TestCase
from pyvcs.porcelain import add, checkout, commit
from pyvcs.repo import repo_create
diff --git a/homework04/tests/test_refs.py b/homework04/tests/test_refs.py
index 8012f57..0dc4337 100644
--- a/homework04/tests/test_refs.py
+++ b/homework04/tests/test_refs.py
@@ -1,8 +1,7 @@
import unittest
-from pyfakefs.fake_filesystem_unittest import TestCase
-
import pyvcs
+from pyfakefs.fake_filesystem_unittest import TestCase
from pyvcs.refs import get_ref, is_detached, ref_resolve, resolve_head, update_ref
from pyvcs.repo import repo_create
diff --git a/homework04/tests/test_repo.py b/homework04/tests/test_repo.py
index 4107078..e534eb1 100644
--- a/homework04/tests/test_repo.py
+++ b/homework04/tests/test_repo.py
@@ -2,7 +2,6 @@
import pathlib
from pyfakefs.fake_filesystem_unittest import TestCase
-
from pyvcs import repo
diff --git a/homework04/tests/test_tree.py b/homework04/tests/test_tree.py
index 1c32a62..8ac69ae 100644
--- a/homework04/tests/test_tree.py
+++ b/homework04/tests/test_tree.py
@@ -4,9 +4,8 @@
import unittest
from unittest.mock import patch
-from pyfakefs.fake_filesystem_unittest import TestCase
-
import pyvcs
+from pyfakefs.fake_filesystem_unittest import TestCase
from pyvcs.index import read_index, update_index
from pyvcs.repo import repo_create
from pyvcs.tree import commit_tree, write_tree