Skip to content

Commit 500c4fc

Browse files
committed
Cleaned code and doc-string
- The CLI for commit, add, rm is still missing or incomplete - The `shortify_sha1` or `abbrev=<n>` function should be added - The CLI for checkout, tag, log should be updated to reflect recent changes - There are still alot of TODO's in the repo, especially re-add the cat-file for commits and tags and trees - Rewriting README to better reflect new changes and bug-fixes in ngit and calc
1 parent fab1dd0 commit 500c4fc

8 files changed

Lines changed: 513 additions & 127 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525
- Added a `ngit ls-files` to parse and show `.git/index`
2626
- Added a `ngit check-ignore` to debug .gitignore and exclude file from `git add`
2727
- Added a semi-complete .gitignore parser (incomplete support for Slash `/` as directory separator)
28+
- Added missing doc-strings and cleaned some code
2829

2930

3031
### Changed

microprojects/ngit/libngit.py

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
1-
import argparse # ngit is CLI tool, so we need to parse CLI args
2-
3-
# import configparser # ngit's config file uses INI format
4-
# from datetime import datetime # to store time of each commit
5-
# import grp, pwd # because ngit saves numerical owner/group ID of files author
6-
# from fnmatch import fnmatch # to match .gitignore patterns like *.txt
7-
# import hashlib # ngit uses SHA-1 hash extensively
8-
# import math
9-
import os # os and os.path provide some nice filesystem abstraction routines
10-
11-
# import re # just a little-bit of RegEx
12-
import sys # to access `sys.argv`
13-
# import zlib # to compress & decompress files
1+
import argparse
2+
import os
3+
import sys
144

155
from microprojects.ngit.object import GitObject, GitCommit, GitTree
166
from microprojects.ngit.repository import GitRepository, repo_file, repo_find_f
17-
from microprojects.ngit.repository import resolve_ref, ref_list, tag_list, GitIgnore
7+
from microprojects.ngit.repository import GitIgnore, resolve_ref, ref_list, tag_list
188
from microprojects.ngit.object_utils import object_find_f, object_read, tag_create
199
from microprojects.ngit.object_utils import gitignore_read, get_obj_type, get_obj_size
2010
from microprojects.ngit.ngit_utils import cat_file, ls_tree, object_hash, repo_create
2111
from microprojects.ngit.ngit_utils import checkout, show_ref, ls_files, check_ignore
2212
from microprojects.ngit.log import print_logs
13+
from microprojects.ngit.status import filter_paths
2314

2415

2516
def ngit_main() -> None:
@@ -41,6 +32,39 @@ def ngit_main() -> None:
4132
arg_subparser.required = True
4233

4334
# ArgParser for ngit add
35+
argsp_add = arg_subparser.add_parser( # add
36+
"add",
37+
prog="ngit add",
38+
description="Add file contents to the index",
39+
help="Add file contents to the index",
40+
formatter_class=argparse.RawTextHelpFormatter,
41+
)
42+
argsp_add.add_argument( # -f --force
43+
"-f",
44+
"--force",
45+
dest="force",
46+
action="store_true",
47+
help="Allow adding otherwise ignored files",
48+
)
49+
argsp_add.add_argument( # -A --all --no-ignore-removal
50+
"-A",
51+
"--all",
52+
"--no-ignore-removal",
53+
dest="all",
54+
action="store_true",
55+
help="All files in entire worktree will be added to GitIndex",
56+
)
57+
argsp_add.add_argument( # --ignore-errors
58+
"--ignore-errors",
59+
dest="no_errors",
60+
action="store_true",
61+
help="If some files could not be added, do not abort the operation, but continue adding the others",
62+
)
63+
argsp_add.add_argument( # paths
64+
"paths",
65+
nargs="*",
66+
help="Files to add content from",
67+
)
4468

4569
# ArgParser for ngit cat-file
4670
argsp_cat_file = arg_subparser.add_parser( # cat-file
@@ -480,6 +504,54 @@ def ngit_main() -> None:
480504
)
481505

482506
# ArgParser for ngit rm
507+
argsp_rm = arg_subparser.add_parser( # rm
508+
"rm",
509+
prog="ngit rm",
510+
description="Remove files from the working tree and from the index",
511+
help="Remove files from the working tree and from the index",
512+
formatter_class=argparse.RawTextHelpFormatter,
513+
)
514+
argsp_rm.add_argument( # -f --force
515+
"-f",
516+
"--force",
517+
dest="force",
518+
action="store_true",
519+
help="Override the up-to-date check",
520+
)
521+
argsp_rm.add_argument( # -q --quiet
522+
"-q",
523+
"--quiet",
524+
dest="quiet",
525+
action="store_true",
526+
help="Suppress all output except errors and warnings",
527+
)
528+
argsp_rm.add_argument( # -r --recursive
529+
"-r",
530+
"--recursice",
531+
dest="recursive",
532+
action="store_true",
533+
help="Allow recursive removal when a leading directory name is given",
534+
)
535+
argsp_rm.add_argument( # --cached
536+
"--cached",
537+
dest="cached",
538+
action="store_true",
539+
help="Use this option to unstage and remove paths only from the index. "
540+
"Working tree files, whether modified or not, will be left alone",
541+
)
542+
argsp_rm.add_argument( # -i --stdin --pathspec-from-file
543+
"-i",
544+
"--stdin",
545+
"--pathspec-from-file",
546+
dest="stdin",
547+
metavar="<path>",
548+
help="Pathspec is passed in <file> instead of args, if <file> is exactly - then standard input is used",
549+
)
550+
argsp_rm.add_argument( # paths
551+
"paths",
552+
nargs="*",
553+
help="Files to add content from",
554+
)
483555

484556
# ArgParser for ngit show-ref
485557
argsp_show_ref = arg_subparser.add_parser( # show-ref
@@ -563,6 +635,55 @@ def ngit_main() -> None:
563635
)
564636

565637
# ArgParser for ngit status
638+
argsp_staus = arg_subparser.add_parser(
639+
"status",
640+
prog="ngit status",
641+
description="Show the working tree status",
642+
help="Show the working tree status",
643+
formatter_class=argparse.RawTextHelpFormatter,
644+
)
645+
argsp_staus.add_argument( # -s --short
646+
"-s",
647+
"--short",
648+
dest="short",
649+
action="store_true",
650+
help="Give the output in the short-format",
651+
)
652+
argsp_staus.add_argument( # -b --branch
653+
"-b",
654+
"--branch",
655+
dest="branch",
656+
action="store_true",
657+
help="Show the branch and tracking info even in short-format",
658+
)
659+
argsp_staus.add_argument( # --porcelain
660+
"--porcelain",
661+
dest="porcelain",
662+
action="store_true",
663+
help="Give the output in an easy-to-parse format for scripts",
664+
)
665+
argsp_staus.add_argument( # --long
666+
"--long",
667+
dest="long",
668+
action="store_true",
669+
help="Give the output in the long-format (default)",
670+
)
671+
argsp_staus.add_argument( # -u --untracked-files
672+
"-u",
673+
"--untracked-files",
674+
dest="untracked",
675+
choices=["all", "normal", "no"],
676+
default="all",
677+
const="all",
678+
nargs="?",
679+
help="Show untracked files",
680+
)
681+
argsp_staus.add_argument( # paths
682+
"paths",
683+
nargs="*",
684+
help="Files to add content from",
685+
)
686+
566687
# ArgParser for ngit tag
567688
argsp_tag = arg_subparser.add_parser(
568689
"tag",

microprojects/ngit/log.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def print_logs(
2828
date_fmt (str): The format to use for dates in `ngit log`.
2929
3030
Returns:
31-
None (None): print_log just has side-effects, no return value
31+
None (None): have side-effect (prints on screen), so returns `None` to enforce this behavior
3232
"""
3333
obj: GitObject = object_read(repo, sha1)
3434

@@ -123,13 +123,13 @@ def prettify(
123123
124124
Parameters:
125125
kvlm (dict): The dict with required information to put in `format_str`
126-
format_str (str): The str containing the required format
126+
format_str (str): The str containing the desired format
127127
shortify: A function that takes in object's ref and returns a shortified sha1 of minimum len 7
128128
commit_sha1 (str): The SHA1 of current commit, as it's not in KVLM
129129
date_fmt (str): The date format to use for dates
130130
131131
Returns:
132-
format_str (str): The format_str, with supported formats replaced by respective value
132+
format_str (str): The format_str, with supported formats replaced by respective values
133133
"""
134134

135135
# Git escape character
@@ -248,8 +248,6 @@ def _ftime(fmt: str) -> str:
248248
"""Convert human-readable/standard time formats to one used by `strftime`"""
249249
fmt = fmt.lower()
250250
match fmt:
251-
case "local":
252-
return ""
253251
case "iso" | "iso8601":
254252
return "%Y-%m-%d %H:%M:%S %z"
255253
case "iso-strict" | "iso8601-strict":
@@ -258,7 +256,7 @@ def _ftime(fmt: str) -> str:
258256
return "%a, %-d %b %Y %H:%M:%S %z"
259257
case "short":
260258
return "%Y-%m-%d"
261-
case "default":
259+
case "default" | "local":
262260
return "%c %z"
263261
case _:
264262
return fmt

microprojects/ngit/ngit_utils.py

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from microprojects.ngit.repository import GitIndex, GitIndexEntry
77
from microprojects.ngit.repository import GitIgnore, gitignore_check_rule
88
from microprojects.ngit.object import GitObject, GitCommit, GitBlob, GitTag, GitTree
9-
from microprojects.ngit.object_utils import object_read, object_find, object_write
10-
from microprojects.ngit.object_utils import object_pick, shortify_hash, index_read
9+
from microprojects.ngit.object_utils import object_read, object_pick, object_write
10+
from microprojects.ngit.object_utils import shortify_hash, index_read
1111

1212

1313
def repo_default_config() -> configparser.ConfigParser:
@@ -181,35 +181,58 @@ def prettify(leaf, format_str: str, obj_fmt: str, _prefix: str) -> str:
181181

182182

183183
def ls_files(repo: GitRepository, fmt: str, endl: str = "\n") -> None:
184-
""""""
184+
"""Show information about files in the index and the working tree in desired `fmt`
185+
186+
Parameters:
187+
repo (GitRepository): The current working git repository
188+
fmt (str): Pretty-print the contents of the tree in this format
189+
endl (str): The separater after each entry
190+
191+
Returns:
192+
None (None): have side-effect (prints on screen), so returns `None` to enforce this behavior
193+
"""
185194

186195
def prettify(entry: GitIndexEntry, format_str: str) -> str:
196+
"""Returns a formatted version of `format_str` using substitutions from entry
197+
198+
Parameters:
199+
entry (GitIndexEntry): The GitIndexEntry to use for filling the `format_str`
200+
format_str (str): The str containing the desired format
201+
202+
Returns:
203+
format_str (str): The format_str, with supported formats replaced by respective values
204+
"""
205+
206+
def to_iso(timestamp: int) -> str:
207+
return datetime.isoformat(datetime.fromtimestamp(timestamp))
208+
187209
obj_type: str = {0o10: "blob", 0o12: "symlink", 0o16: "commit"}[entry.mode_type]
210+
obj_mode: str = f"{entry.mode_type:02o}{entry.mode_perms:04o}"
211+
flags: str = f"{entry.flag_assume_valid}{entry.flag_stage}"
188212

189-
format_str = format_str.replace("%(objectmode)", f"{entry.mode_type:02o}{entry.mode_perms:04o}") # fmt: skip
190-
format_str = format_str.replace("%(objecttype)", f"{obj_type}")
191-
format_str = format_str.replace("%(objectname)", f"{entry.sha1}")
192-
format_str = format_str.replace("%(objectsize)", f"{entry.file_size}")
193-
format_str = format_str.replace("%(stage)", f"{entry.flag_stage}")
194-
format_str = format_str.replace("%(path)", f"{entry.name}")
213+
format_str = format_str.replace("%(objectmode)", obj_mode)
214+
format_str = format_str.replace("%(objecttype)", obj_type)
215+
format_str = format_str.replace("%(objectname)", entry.sha1)
216+
format_str = format_str.replace("%(objectsize)", str(entry.file_size))
217+
format_str = format_str.replace("%(stage)", str(entry.flag_stage))
218+
format_str = format_str.replace("%(path)", entry.name)
195219

196220
# Extras
197221
format_str = format_str.replace("%(ctime)", f"{entry.ctime_s}:{entry.ctime_n}")
198222
format_str = format_str.replace("%(mtime)", f"{entry.mtime_s}:{entry.mtime_n}")
199-
format_str = format_str.replace("%(ctime:iso)", f"{datetime.fromtimestamp(entry.ctime_s)}") # fmt: skip
200-
format_str = format_str.replace("%(mtime:iso)", f"{datetime.fromtimestamp(entry.mtime_s)}") # fmt: skip
201-
format_str = format_str.replace("%(dev)", f"{entry.dev}")
202-
format_str = format_str.replace("%(ino)", f"{entry.ino}")
203-
format_str = format_str.replace("%(uid)", f"{entry.uid}")
204-
format_str = format_str.replace("%(gid)", f"{entry.gid}")
205-
format_str = format_str.replace("%(gid)", f"{entry.gid}")
206-
format_str = format_str.replace("%(flags)", f"{entry.flag_assume_valid}{entry.flag_stage}") # fmt: skip
223+
format_str = format_str.replace("%(ctime:iso)", to_iso(entry.ctime_s))
224+
format_str = format_str.replace("%(mtime:iso)", to_iso(entry.mtime_s))
225+
format_str = format_str.replace("%(dev)", str(entry.dev))
226+
format_str = format_str.replace("%(ino)", str(entry.ino))
227+
format_str = format_str.replace("%(uid)", str(entry.uid))
228+
format_str = format_str.replace("%(gid)", str(entry.gid))
229+
format_str = format_str.replace("%(gid)", str(entry.gid))
230+
format_str = format_str.replace("%(flags)", flags)
207231

208232
return format_str
209233

210-
index: GitIndex = index_read(repo)
211-
for entry in index.entries:
212-
print(prettify(entry, fmt))
234+
for entry in index_read(repo).entries:
235+
print(prettify(entry, fmt), end=endl)
213236

214237

215238
def checkout(repo: GitRepository, tree: GitTree, path: str, quiet: bool) -> None:
@@ -259,7 +282,15 @@ def show_ref(repo: GitRepository, refs: list, only_sha1: bool, deref: bool, pref
259282

260283

261284
def check_ignore(rules: GitIgnore, path: str) -> bool:
262-
""""""
285+
"""Check whether the `path` should be ignored under given GitIgnore `rules`
286+
287+
Parameters:
288+
rules (GitIgnore): The parsed GitIgnore rules to ckeck against
289+
path (str): Relative path to file of which we want to check ignore status of
290+
291+
Returns:
292+
status (bool): True if the path will be ignored, False otherwise
293+
"""
263294

264295
if os.path.isabs(path):
265296
raise ValueError(f"{path=} should be relative, not absolute")

microprojects/ngit/object.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ def deserialize(self, data: bytes) -> None:
6868

6969

7070
class GitCommit(GitObject):
71-
"""TODO: Add description here
71+
"""GitCommits are represented as key-value pairs (RFC2822)
7272
7373
Attributes:
74-
data (dict):
74+
data (dict[None | bytes, list[bytes] | bytes]): key-value pairs (list[bytes] is preferred)
7575
fmt (bytes): GitCommit uses "commit" in header format
7676
"""
7777

@@ -89,14 +89,15 @@ def init(self) -> None:
8989

9090

9191
class GitTag(GitCommit):
92-
"""Tags are similar to commits
92+
"""Like GitCommit, GitTag are also key-value pairs (RFC2822)
9393
9494
Attributes:
95-
data (dict):
95+
data (dict[None | bytes, list[bytes] | bytes]): key-value pairs (list[bytes] is preferred)
9696
fmt (bytes): GitTag uses "tag" in header format
9797
"""
9898

9999
fmt: bytes = b"tag"
100+
# all other stuff is same as GitCommit
100101

101102

102103
class GitTree(GitObject):

0 commit comments

Comments
 (0)