From cf661af751fa556224556d6b50f820305ba5d8ba Mon Sep 17 00:00:00 2001 From: StupidJohanna Date: Fri, 27 Oct 2023 11:30:33 +0200 Subject: [PATCH] initial commit --- .gitignore | 4 + .signore | 1 + .sing/branches/foo/.signore | 1 + .sing/branches/foo/foobar | 0 .../hooks/__pycache__/diff.cpython-311.pyc | Bin 0 -> 2661 bytes .../hooks/__pycache__/remote.cpython-311.pyc | Bin 0 -> 16405 bytes .sing/branches/foo/hooks/diff.py | 41 ++ .sing/branches/foo/hooks/remote.py | 234 ++++++++++ .../foo/lib/__pycache__/abc.cpython-311.pyc | Bin 0 -> 6003 bytes .../foo/lib/__pycache__/db.cpython-311.pyc | Bin 0 -> 2594 bytes .../lib/__pycache__/dirstat.cpython-311.pyc | Bin 0 -> 2998 bytes .../__pycache__/keyexchange.cpython-311.pyc | Bin 0 -> 1501 bytes .sing/branches/foo/lib/abc.py | 94 ++++ .sing/branches/foo/lib/db.py | 30 ++ .sing/branches/foo/lib/dirstat.py | 51 +++ .sing/branches/foo/lib/keyexchange.py | 20 + .sing/branches/foo/sing.py | 419 +++++++++++++++++ .sing/branches/foo/test.py | 1 + .sing/branches/master/.signore | 1 + .../hooks/__pycache__/diff.cpython-311.pyc | Bin 0 -> 2661 bytes .../hooks/__pycache__/remote.cpython-311.pyc | Bin 0 -> 16405 bytes .sing/branches/master/hooks/diff.py | 41 ++ .sing/branches/master/hooks/remote.py | 234 ++++++++++ .../lib/__pycache__/abc.cpython-311.pyc | Bin 0 -> 6003 bytes .../master/lib/__pycache__/db.cpython-311.pyc | Bin 0 -> 2594 bytes .../lib/__pycache__/dirstat.cpython-311.pyc | Bin 0 -> 2998 bytes .../__pycache__/keyexchange.cpython-311.pyc | Bin 0 -> 1501 bytes .sing/branches/master/lib/abc.py | 94 ++++ .sing/branches/master/lib/db.py | 30 ++ .sing/branches/master/lib/dirstat.py | 51 +++ .sing/branches/master/lib/keyexchange.py | 20 + .sing/branches/master/sing.py | 419 +++++++++++++++++ .sing/branches/master/test.py | 1 + .sing/control/COMMIT-0.commit | 8 + .sing/control/COMMIT-1.commit | 8 + .sing/control/COMMIT-2.commit | 8 + .sing/control/COMMIT-3.commit | 8 + .sing/control/COMMIT-4.commit | 8 + .sing/control/COMMIT-5.commit | 8 + .sing/control/COMMIT-6.commit | 8 + .sing/control/COMMIT-7.commit | 8 + .sing/control/COMMIT-8.commit | 9 + .sing/stage/.signore | 1 + .sing/stage/foobar | 0 .../hooks/__pycache__/diff.cpython-311.pyc | Bin 0 -> 2661 bytes .../hooks/__pycache__/remote.cpython-311.pyc | Bin 0 -> 16405 bytes .sing/stage/hooks/diff.py | 41 ++ .sing/stage/hooks/remote.py | 234 ++++++++++ .../stage/lib/__pycache__/abc.cpython-311.pyc | Bin 0 -> 6003 bytes .../stage/lib/__pycache__/db.cpython-311.pyc | Bin 0 -> 2594 bytes .../lib/__pycache__/dirstat.cpython-311.pyc | Bin 0 -> 2998 bytes .../__pycache__/keyexchange.cpython-311.pyc | Bin 0 -> 1501 bytes .sing/stage/lib/abc.py | 94 ++++ .sing/stage/lib/db.py | 30 ++ .sing/stage/lib/dirstat.py | 51 +++ .sing/stage/lib/keyexchange.py | 20 + .sing/stage/sing.py | 419 +++++++++++++++++ .sing/stage/test.py | 1 + .sing/system/.keyfile | 5 + .sing/system/branchcf.cson | 1 + .sing/system/localconfig.cson | 1 + .sing/system/sing.db | Bin 0 -> 336354 bytes hooks/__pycache__/diff.cpython-311.pyc | Bin 0 -> 2661 bytes hooks/__pycache__/remote.cpython-311.pyc | Bin 0 -> 17296 bytes hooks/diff.py | 41 ++ hooks/remote.py | 249 ++++++++++ lib/__pycache__/abc.cpython-311.pyc | Bin 0 -> 6003 bytes lib/__pycache__/db.cpython-311.pyc | Bin 0 -> 2594 bytes lib/__pycache__/dirstat.cpython-311.pyc | Bin 0 -> 2998 bytes lib/__pycache__/keyexchange.cpython-311.pyc | Bin 0 -> 1501 bytes lib/abc.py | 94 ++++ lib/db.py | 30 ++ lib/dirstat.py | 51 +++ lib/keyexchange.py | 20 + sing.py | 426 ++++++++++++++++++ test.py | 1 + 76 files changed, 3670 insertions(+) create mode 100644 .gitignore create mode 100644 .signore create mode 100644 .sing/branches/foo/.signore create mode 100644 .sing/branches/foo/foobar create mode 100644 .sing/branches/foo/hooks/__pycache__/diff.cpython-311.pyc create mode 100644 .sing/branches/foo/hooks/__pycache__/remote.cpython-311.pyc create mode 100644 .sing/branches/foo/hooks/diff.py create mode 100644 .sing/branches/foo/hooks/remote.py create mode 100644 .sing/branches/foo/lib/__pycache__/abc.cpython-311.pyc create mode 100644 .sing/branches/foo/lib/__pycache__/db.cpython-311.pyc create mode 100644 .sing/branches/foo/lib/__pycache__/dirstat.cpython-311.pyc create mode 100644 .sing/branches/foo/lib/__pycache__/keyexchange.cpython-311.pyc create mode 100644 .sing/branches/foo/lib/abc.py create mode 100644 .sing/branches/foo/lib/db.py create mode 100644 .sing/branches/foo/lib/dirstat.py create mode 100644 .sing/branches/foo/lib/keyexchange.py create mode 100644 .sing/branches/foo/sing.py create mode 100644 .sing/branches/foo/test.py create mode 100644 .sing/branches/master/.signore create mode 100644 .sing/branches/master/hooks/__pycache__/diff.cpython-311.pyc create mode 100644 .sing/branches/master/hooks/__pycache__/remote.cpython-311.pyc create mode 100644 .sing/branches/master/hooks/diff.py create mode 100644 .sing/branches/master/hooks/remote.py create mode 100644 .sing/branches/master/lib/__pycache__/abc.cpython-311.pyc create mode 100644 .sing/branches/master/lib/__pycache__/db.cpython-311.pyc create mode 100644 .sing/branches/master/lib/__pycache__/dirstat.cpython-311.pyc create mode 100644 .sing/branches/master/lib/__pycache__/keyexchange.cpython-311.pyc create mode 100644 .sing/branches/master/lib/abc.py create mode 100644 .sing/branches/master/lib/db.py create mode 100644 .sing/branches/master/lib/dirstat.py create mode 100644 .sing/branches/master/lib/keyexchange.py create mode 100644 .sing/branches/master/sing.py create mode 100644 .sing/branches/master/test.py create mode 100644 .sing/control/COMMIT-0.commit create mode 100644 .sing/control/COMMIT-1.commit create mode 100644 .sing/control/COMMIT-2.commit create mode 100644 .sing/control/COMMIT-3.commit create mode 100644 .sing/control/COMMIT-4.commit create mode 100644 .sing/control/COMMIT-5.commit create mode 100644 .sing/control/COMMIT-6.commit create mode 100644 .sing/control/COMMIT-7.commit create mode 100644 .sing/control/COMMIT-8.commit create mode 100644 .sing/stage/.signore create mode 100644 .sing/stage/foobar create mode 100644 .sing/stage/hooks/__pycache__/diff.cpython-311.pyc create mode 100644 .sing/stage/hooks/__pycache__/remote.cpython-311.pyc create mode 100644 .sing/stage/hooks/diff.py create mode 100644 .sing/stage/hooks/remote.py create mode 100644 .sing/stage/lib/__pycache__/abc.cpython-311.pyc create mode 100644 .sing/stage/lib/__pycache__/db.cpython-311.pyc create mode 100644 .sing/stage/lib/__pycache__/dirstat.cpython-311.pyc create mode 100644 .sing/stage/lib/__pycache__/keyexchange.cpython-311.pyc create mode 100644 .sing/stage/lib/abc.py create mode 100644 .sing/stage/lib/db.py create mode 100644 .sing/stage/lib/dirstat.py create mode 100644 .sing/stage/lib/keyexchange.py create mode 100644 .sing/stage/sing.py create mode 100644 .sing/stage/test.py create mode 100644 .sing/system/.keyfile create mode 100644 .sing/system/branchcf.cson create mode 100644 .sing/system/localconfig.cson create mode 100644 .sing/system/sing.db create mode 100644 hooks/__pycache__/diff.cpython-311.pyc create mode 100644 hooks/__pycache__/remote.cpython-311.pyc create mode 100644 hooks/diff.py create mode 100644 hooks/remote.py create mode 100644 lib/__pycache__/abc.cpython-311.pyc create mode 100644 lib/__pycache__/db.cpython-311.pyc create mode 100644 lib/__pycache__/dirstat.cpython-311.pyc create mode 100644 lib/__pycache__/keyexchange.cpython-311.pyc create mode 100644 lib/abc.py create mode 100644 lib/db.py create mode 100644 lib/dirstat.py create mode 100644 lib/keyexchange.py create mode 100644 sing.py create mode 100644 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0b3243 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +.sing/ +.signore/ + diff --git a/.signore b/.signore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.signore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/.sing/branches/foo/.signore b/.sing/branches/foo/.signore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.sing/branches/foo/.signore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/.sing/branches/foo/foobar b/.sing/branches/foo/foobar new file mode 100644 index 0000000..e69de29 diff --git a/.sing/branches/foo/hooks/__pycache__/diff.cpython-311.pyc b/.sing/branches/foo/hooks/__pycache__/diff.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0edd7bc77759107a76fce8a63815b06d2e968e6e GIT binary patch literal 2661 zcmc&$OKcNI7@mE3Z6|98N$ia=&PJpuPB7sm(N+YB7LrQ{L_wt(A!NMkcpZD!+TB27 z9Ar==P!*{L1;t9CSXB!~aLA#D969#TL$+F})=HL;svLUTlvZsyb$0zo9O6nymHLlo z=Ksh4>-*6ZW*7gL08n9O=im)W- zh@z~0j%c(EIay=^1RTGbieoy?^3iAj(QzT1UvdTp2DMBL$ z>}H_XU*i^dWD=Q06u3~McwM`I*48%9EZPWLI+{TW_0%_ODrT%IZM7=Y3t7`J=SHxN zaS)(T8@lm&p*Kfqf}>vS4SN7LoyAzZvG9!%8>Lw^gR(?d-Nh`H0Xal^rqt^NK)jCl z9W$|Iuz${zRn;=-)-sk_6xdnPl=8h(r z|JkrZ&h52w$b8H8XRq-VodTCHzT3(9uPY?Gt7mCf1Fn|sam|0mwoU-E2dMN}Cj;pd zF_{WW9CI?F@Yguzf+$V#!X$Hwm$--`N;6DYV3|Z%;beu0h{+_cFrvUHF^-YpyU1LX z!a^iwc}f!pWN4bU!)a0wB~G`=GqS=Z(^N$(5{v*}h zr@8xnb|3u7eNc5D*4&3x`w`84WYvD8OtsBle4sq~_~FRo(WTL%XL;YsS=BeH`9@XG z8O?J>rN%UBEIV9sb*y&2r@8vFBPG(c4mt^@+}6I}-DKInGO7B8HQ%t>HlnqS7{KM| zUpI%lC(G@f3&$2u+&@w1QQLd9_Fe-}RDW@NxvzMsc&WUzGZ)Jr%EfZAdpj0(D7@s;;q9#ZMk8hv^lLuvQ#01j)kU!w!X9+lp& z(ffZRP&#P9_23^i(B;p^3*-5CKCaRMsI+Q*%3a-BSATQAUemAlZNFaAueZ71HG0om z;r_NNbr1A0-ZE3B)p%x?QBZn;myLYiekb#ZO?$@H?4hecw;}F6xrzncS7oeqWPOD zFT%mV5CkcK)~wY^pe1Xy5^!X#Rsznf)k=WMTJ06ND?2@RBX3(JyH&DVBfGP>VFRe` zj_hi!`8z2;?d>ZDm|ppLk9385GNdR9UHvGtz!V)Ip;UfnufJ%CH@b7 CEGRet literal 0 HcmV?d00001 diff --git a/.sing/branches/foo/hooks/__pycache__/remote.cpython-311.pyc b/.sing/branches/foo/hooks/__pycache__/remote.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21d643fd1a348b77b662ad151c30d84e960d8d0f GIT binary patch literal 16405 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 shutil.disk_usage(i): + print(f"+++ {i}") + elif shutil.disk_usage( + os.path.join(".sing", "branches", masterb, i) + ) < shutil.disk_usage(i): + print(f"--- {i}") + else: + print(f"=== {i}") + for i in os.listdir(): + if ( + not os.path.exists(os.path.join(".sing", "branches", masterb, i)) + and not i in ignore + ): + print(f"+ {i}") diff --git a/.sing/branches/foo/hooks/remote.py b/.sing/branches/foo/hooks/remote.py new file mode 100644 index 0000000..5594708 --- /dev/null +++ b/.sing/branches/foo/hooks/remote.py @@ -0,0 +1,234 @@ +import typer +import socket +import cson +import os +import sys +import pickle +from lib.db import Database +from lib.abc import FileWrap, Key +from lib.keyexchange import generate_keys + + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + + +remote = typer.Typer(name="remote") + +cstep = 0 + +CHUNK_SIZE = 1 + + +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i : i + n] + + +def cycle(): + global cstep + steps = ["/", "-", "\\", "|"] + cstep += 1 + if cstep == 4: + cstep = 0 + return steps[cstep] + + +@remote.command() +def add(name: str, url: str): + """ + Add a remote named for the repository at . The command 'sing remote fetch' can then + be used to create and update remote branches (/) + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["remotes"][name] = url + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@remote.command(name="keys") +def kg( + generate: bool = typer.Option(True, "-g", "--generate-new"), + publish: bool = typer.Option(False, "-p", "--publish"), +): + if generate: + generate_keys() + print("REMINDER : THIS KEY WILL BE USED TO ENCRYPT REMOTE PUSHES.") + print("Other Clients will not be able to decrypt the Push unless they") + print("receive the key. Use these commands to share your keys:") + print("\tsing remote keys --publish") + + if publish: + if not os.path.exists(os.path.join(".sing", "system", ".keyfile")): + return print( + "Fatal -- no Keys found. Use the '-g' option to create new keys" + ) + print("Importing Keys...") + key: Key = Key.kimport(os.path.join(".sing", "system", ".keyfile")) + print(key.randomart) + import getpass + + print("Do you want to protect your Keys using a Passphrase?") + print("Recipients will be prompted for their Password before getting the Key") + p = input("[y/N]") or "n" + p = p.lower() + if p in ["y", "yes"]: + passphrase = getpass.getpass("Enter Passphrase : ") + pass_rep = getpass.getpass("Re-Type Passphrase:") + i = 1 + while not pass_rep == passphrase and i < 3: + pass_rep = getpass.getpass( + "Wrong Passphrase - Please re-type Passphrase:" + ) + i += 1 + if i >= 3: + return print("Aborted - 3 Times entered Wrong Password") + + +@remote.command(name="get-url") +def gurl(remote_name): + """ + Retrieves the URLs for a Remote. + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + rmurl = config["remotes"].get(remote_name, "Fatal -- No Remote Found.") + print(rmurl) + + +def clone(url, init_fn, pull_fn): + """ + Download objects from remote repository + """ + import urllib.parse + + parsed = urllib.parse.urlparse(url) + if os.path.exists(parsed.path.split("/")[-1]): + return print(f"Fatal -- File/Directory exists: {parsed.path.split('/')[-1]}") + print("Connecting to remote Server...") + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(parsed.hostname), 2991)) + s = f"down {parsed.path}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {parsed.path}".encode()) + + database = [] + print("Initializing Repository...") + os.mkdir(parsed.path.split("/")[-1]) + os.chdir(parsed.path.split("/")[-1]) + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + init_fn(".", Branch_Config["current_branch"], True) + os.chdir("..") + try: + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + except: + config = {} + config = Branch_Config + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{url}/HEAD -> local/HEAD") + print("Pulling changes...") + pull_fn() + + +@remote.command() +def fetch(remote_name): + """ + Download objects from remote repository + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"down {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {lconfig['repo.name']}".encode()) + + database = [] + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["branches"] = Branch_Config["branches"] + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{remote_name}/HEAD -> local/HEAD") + print("Use 'sing pull' to write into local files") + + +@remote.command() +def push(remote_name): + """ + Update remote refs along with associated objects + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"up {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(8) + sock.send(s.encode()) + db = { + "MainDB": open(os.path.join(".sing", "system", "sing.db"), "rb").read(), + "Branches": config, + "CommitHistory": [ + FileWrap(i, open(os.path.join(".sing", "control", i), "rb").read()) + for i in os.listdir(os.path.join(".sing", "control")) + ], + } + db = pickle.dumps(db) + c = list(chunks(db, CHUNK_SIZE)) + for i, chunk in enumerate(c): + print(f"Sending Objects... {i}/{len(c)-1} {cycle()}", end="\r") + sock.send(chunk) + print() + print(f"Origin -> HEAD[{remote_name}]") diff --git a/.sing/branches/foo/lib/__pycache__/abc.cpython-311.pyc b/.sing/branches/foo/lib/__pycache__/abc.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5315cc7f0e2664f80e557c1cc730985d1389aed GIT binary patch literal 6003 zcmcIo-ESMm5x>0;@dYSR$U7;4 zTC_{v-0sfY?%d38XJ_xvfq;*LQu_QEwIFlcKd@mHu~FIi3slxQg;Pj|yGnR&hEw>P zoFd%eIqp7OX1U5UjtCsd#Ss|C101i5BQlN*oHiFnVjLfE+FcwE^W)vOYd$( zK08>7*d05lTuh*dOG8dAWc6e^s~K$xn!J%bY@beSdqiAJL|z@D;{ z$c5c%;iy^+$SPOu>Me+CZB?ep-6>F@+;g(ha}qiO)!rY$Em(jb4S_U8dl(+7>Ps~1 za3nerC*Sdb^v?{#-&!DfS6gFj-BG(oc$F{up+F8d0qjCDPo@F;Jw%dk0??O zXkI|7+Yp5wQTLc5B6ThX3gFBVh2S%a7ikWRv%>HD`F;SISVA1_DtV&FIN}|6yZiPV zn&Yp%o#$~m#0_oRGO6%&pX86(? z_*+kAQ>wXu+BBU40MoO}AlbnrGwOS4#vqy@%&E(U^jKiWijaK?bYg~KV7$YCOR5)EcQ1u6DJc;yt)6uk=P1R`YP32kTxTaOPbnju+_y3Kr|xnnRtd#w90`n7!;#{p^~9%%(xtnv z{ozXaz-Z;bXgNGuB>$?0!UdVZF&-lfkq~s`L;_bqB4PLviTMR(DT8%?BJu80GSlD? z-4I_+Yq}v_%BB~xv zY>uC;482qtJXhnO*G+suDN^HrluonT_Y!kz#DybbTfp9#0FE^iUj&Vl;%9fgyw*fOPHJcjq|1)ZwN z`354aM{z8yGeqEWaU%jfMn`~g54O%(*x~Q-rgxr#zQ*p!HY3w7;l?z!ThMS0JXV9&Ny!vN_}?OEu^1uWH= zEFxtG^BvGj?nb@VLK|J1@*(@rj1F(hmJlEo6>9+q8jIqZObQa^p!jf)y8@+o33>zLt*nG|mjxBH7JFR|s9;E3DK&<6#ckH#CEv;3q+50j%NqF;dL4G|Wra*wdJrlEn&l#fnB6`j(Mqn<1$9A{<~M$?aOg?a}(K#Yry(p z#JZc9SjX8MSVx;;*++zHEH3Q-BM308F=x{k zp|%SFeoH3=z-g2~Rbj0F0G2=b;MND_&_E?Luqh9imc`uSdt%sLzGt>Xg3Du{F)WIH zbrW-T9BFUAjsd4NhC!r)D2Dr%io2LAoq#BYd5&ZV=hWpChIy;liw_~>-ou0YS)c6n#?K-^GT}P+4i z)U_kq8I;0-!CM`;^LVSB2jM0+_Qz`P5ok4!WR4(%!5_8VsR3E%bON_Vfq-MT6ynU+ zO4dTMgxOu$R}Kf`iq>Oz7E-`|&7|~q(Eh!!pIneVEACg)dEzp+{FoHa2{*1io;x-? zJRJS`^vv0K^u_7(@#%}v)90_edhwN+^QT@tb0vCi`brcee6%0b%^mfj^mKgchh!l` z5*HTLEd2#MVs7i0A+$KxwG83g$I|>_PDamS4FRC70$Jt0mi>i`YnO{^S$?b{KejC& z*^-Y`{ZDQCC${_(W&dQwKUocStX;1QB+&g$Fj^eH{p@BiYW}x_$G3vV%fVPB7=z~aTc&8&WJG&yD9>j0eaF~Ks+iU4h)+1?KhRzyWqq>MN(tw{5p6%WC*&;ys07o7OW zi_W)L@nQlAsfqyW+j|G_d*8+|ro(ZAr`IT^%@5F0<{6VW+7-H#ol~;FwxPjPCa zf1|&2q1+p*^v3Rvm%GL)U1No|YN(^26`v?-8&joNIW$xW4Q~@{R&tV^u8MriDf~)qg#*5OAYn)B|9tj|ClQ8Fr)OfEnT;qV$ z1!&%UquIfF%}of$2X}b%daAyuwp)B4xKT?D=e>?U>Wd)Q=*2;>Zb&k1|dI_rPf$~`C%I3w&Pb?k#!U~$83 p1oErhq>g6EN>?QpdiBdoY;6{|(*UXu|*i literal 0 HcmV?d00001 diff --git a/.sing/branches/foo/lib/__pycache__/db.cpython-311.pyc b/.sing/branches/foo/lib/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f94b661849dc70375d0ab75ece313a4a084acaba GIT binary patch literal 2594 zcmb_d-%BG`6ux(UWhN#@-Q8BBxNhyTjbdWi3R~DN2uq8*;6CKZDRf9O<2G)7*gF%f zA%YK~Xc1~bRtrAlq1cLj>q}o+@IO%SK?a0EpSDjC_d)il=iEug#A>0i_c~wBJ?Gx* zneTpc?oZ)xkU*>b^1%9AAmlF`G@H~g&K`oXNlaqW9MOp>m|~9R1zpICx|o-Ai4uW) zMog(nOnC=(*@MSxx@^)Ui7BseIQ@zO#za@0dX{vUmCCz;six8JWW04AHn%zZ6vmsx zB04pRE|{7rRwZ4;f2d2Q3@DpHQ>jv2F#|9PSgNU3$quaA^Y_y=Q-D-DT=j9vNu^V^ z#3lO)6BLt;w{!q?&qS)i265DMLkP?9CByUcLEH*;5*E{iNZ z8k1dNw%|&|d8^A{YdyVn_xsx$x9fz4KdL=B8F+8|>CeH#f%``T_t&KjtuBI}2RWSLwATn1zH6=X z^Yd$CJaAr@sMden8(n$4HnqY#tg*)+(bcZWF!>=mNyJ|}z}hF$q!iefM6?4GZI#<$ zZL&|y_y-Fs(Z=U23ILK3Dr62UuT?01K6qribUH>^7mQ*e>xLg!d|@rRvQ=={Vv}%o z0|W8`P;HeveOmdr6GApF1Wx+~fm3aLc(ro{`WZM$#upb1d>L=RV3Rm4G04szT_Iq4 zjezOf6)@|7#9}fF!w;7q!`EXG1kRDbIbuEFu<-*O8uGYNO7kFHs~qvk@f=8u{SCk} zISF^KzrS&BYvM3`=O}y!PF@?Y=_kE|+a0whhrNlT-UOaMC`}vZK@OlaZ5))=>br|Ov6R^-Oo;&)5mE^Q@i_YNZ? zN0E^Ob%dd^uh!Q~h`bejkvU|9x5}lmiY4#=m&h0v!38XWmqZ4&uauu}Rhg?bN?*3n zuC+`tpU*liTJs#pmAwjZrPkuLnUhe@+S4s*TdRG17#ceYjU9x>P;POQkq0QZILfWX zcM-j0HRN{vR8MaGkHkNuNg=Q=Q^2-+%`Z}z`zGAj6}a2lKk0^?c0o|Cn913$JX=WR ztu{sinPQ<}Wt?)CFQA5J06D=gcbWVd=~=PYCsxaAO zaB#1eAf^>lc(rrIR64^Xa4<}iVbSJkUwWT6>O+sBM=TCgusYaZ0Dz(>JtpC0-jB)X W!S#Q2rH?{#|Atfay}lyA)BOkbk`GA$ literal 0 HcmV?d00001 diff --git a/.sing/branches/foo/lib/__pycache__/dirstat.cpython-311.pyc b/.sing/branches/foo/lib/__pycache__/dirstat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..120cf41396909ad35a2a65ce7a15ed5dc9cfa418 GIT binary patch literal 2998 zcma(T-)q}O_)e04%dwm$Oy5hg(j&cYNmOFMomZOzUwoCL6k8jfmxwlNXtt4Uf;Fp@Qx7_#1eyZv zTotO+1+>@+%N zQfTfqb?OxFGpQ9B`)E;2Ak&jf$O>6789`YzT?v^Wp^SiJ=@wo}ab*RqneK!b6% zWJOR;W!4a}gawG%<$D<9!5-I{pQ$qLjp@R*Z(l|C$7l4&m=PH(%#?)9%Z1Bi_x0_YTQ_$v>cM^^*k1#X z2o7jao47Z5fAV=`>{({+Bm`t2H(9$OaQ z4=g~H{sDdlz5_V;nHVA$LByhnDB@Ww@^H8-Qc5=c2|1C*ahZ&D^t6qQ6`+eu#o}IoseY>JIrH6SV%i{U zwcM5df9D-+3dM&?q~#p^Kf`G=ttB^G^PCPdVlZj@4xB$=+%GU-N4T1ZJ&!kO-DIwQoFQJ9Fi zR&igEP!z9u1`Xi}4HGa(zzGawa(FY{MhTsO_XzJ#3~T0^JGYhFy`qN(jL-n?F2TVHcXTIsC%7x6_1{aZ6uG%BE*gkOf6Sh?FJl z8}gs+)_mIZB~kX4h1GksRi%j8o&ZaMRl8n$FQit)Y*P9Noy9ED+vMLcsL>F@3OG?d z?5zM_-g+v4E8FiL?a!Ytd{|o0>3)MIH4F|_LcPVW^V9k1y-=?aI+35QxdG*`Q9js> rJp#M`rZKwFTPO5dEI*9afDIaaLO5>mS;&`qY5-47&=7M_t&jW{gyTLf literal 0 HcmV?d00001 diff --git a/.sing/branches/foo/lib/__pycache__/keyexchange.cpython-311.pyc b/.sing/branches/foo/lib/__pycache__/keyexchange.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e951aa06ff293c6fb5ed0caa6154ef2072a3e66 GIT binary patch literal 1501 zcmaJ>-)q};5dTX0TDGMq&1PpmTAI2J-6kjw8zV_awwILAWzdIZgP}eI)%w;}BFW~f zZ`Rq!ppC*x+2~$M4Wro6KJBE`zuK_#>3nzJyYqeTbMl{x zA|ar`^G)&{kI;+M7!%kToqi8rPmzHP&O$b)aa^kN7HKvMjDg585 zC%kd`?zFzgdC21-24**$%&&($G?=;lG=3h5*ZL@W+D)6dlAz;s(P59cgRcB-lT=_yDyCcMzVpiX(5+D2FTE|zMJM$fL^Yy*$!LVg*zQuIyG5m%`)$&D})M_c{;;Jv>-3+44lUiWXtvhsN2 z(Z-XFXP^JG9$xzR-*1A_W?0&cK*~2F`G$XUv{Zg_XHW{4uSLt(1}t2<9xYw>zZ&6U zAYJYYAb>->65*8quVn6Oh*u-L8sJqhAF3fPN4Ol|@<>u18ozJ%%l%F$sgb1m+cD1n za_i??KkYo&@prQ1tNl*{=_+uDs}ZgSxEjlg{>~{}3aN`psZKj>)@o3_-R#uvW&a&# z5S7p7xCcPiCL_&Y$N)*iGiL@CYzhNp8Z)4!?^Gh_ck3Bi*);z~^b&aK`ygHxw(OR1 zXpt{y70mQ)bbkYJB5)iRqYr|4j?pK str: + s = "" + for key, value in self.contains.items(): + s += ( + "├" + + "─" + + "─" * (4 * level) + + " " + + key + + ("/" if isinstance(value, DirWrap) else "") + + "\n" + ) + if isinstance(value, DirWrap): + s += value.stringify(level + 1) + return s + + +class FileWrap: + def __init__(self, fname: str, fdata: t.Union[bytes, str]) -> None: + self.name = fname + self.data = fdata.encode() if isinstance(fdata, str) else fdata + + +######### +# Streams +# + + +class IStream: + def __init__(self, file) -> None: + self.file = file + + def write(self, *data): + self.file.write(*data) + + +class IOStream: + def __init__(self, fdin: IStream, fdout: "OStream") -> None: + self.fdin = fdin + self.fdout = fdout + + def write(self, *data): + self.fdin.write(*data) + + def read(self): + return self.fdout.read() + + +class OStream: + def __init__(self, file) -> None: + self.file = file + + def read(self): + return self.file.read() + + +class Key: + def __init__(self, kpath, key, hash, randomart): + self.kp = kpath + self.key = key + self.hash = hash + self.randomart = randomart + + def dump(self): + open(self.kp, "wb+").write( + "--- BEGIN FERNET CRYPTOGRAPHY KEY ---\n".encode() + + self.key + + f"\n{self.hash}\n".encode() + + "\n--- END FERNET CRYPTOGRAPHY KEY ---".encode() + ) + + @classmethod + def kimport(cls, kp): + k = open(kp, "rb").read().splitlines() + key = k[1] + hash = k[2].decode() + from random_art.randomart import drunkenwalk, draw + + randomart = draw(drunkenwalk(key), hash) + return cls(kp, key, hash, randomart) diff --git a/.sing/branches/foo/lib/db.py b/.sing/branches/foo/lib/db.py new file mode 100644 index 0000000..ee68c23 --- /dev/null +++ b/.sing/branches/foo/lib/db.py @@ -0,0 +1,30 @@ +import pickle +from .abc import IOStream, IStream, OStream + + +class Database: + def __init__(self, fn) -> None: + self.fn = fn + try: + with open(fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + except: + self.data = {} + + def write(self, key, entry): + self.data[key] = entry + + def update(self): + with open(self.fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + + def get(self, key, default=None): + return self.data.get(key, default) + + def commit(self): + with open(self.fn, "wb+") as stream_in: + pickle.dump(self.data, stream_in) + + @classmethod + def connect(cls, fname): + return cls(fname) diff --git a/.sing/branches/foo/lib/dirstat.py b/.sing/branches/foo/lib/dirstat.py new file mode 100644 index 0000000..bf4f84d --- /dev/null +++ b/.sing/branches/foo/lib/dirstat.py @@ -0,0 +1,51 @@ +from .abc import FileWrap, DirWrap +import os +import functools + +if hasattr(functools, "cache"): + cache_fn = functools.cache +else: + cache_fn = functools.lru_cache + + +@cache_fn +def parse_directory(dirp): + structure = {} + os.chdir(dirp) + for d in os.listdir(): + if os.path.isdir(d): + structure[d] = parse_directory(d) + elif os.path.isfile(d): + structure[d] = open(d, "rb").read() + os.chdir("..") + return structure + + +@cache_fn +def wrap_directory(dirp, level=0): + structure = parse_directory(dirp) + data = [] + for k, v in structure.items(): + if isinstance(v, dict): + data.append(wrap_directory(k, level + 1)) + else: + data.append(FileWrap(k, v)) + if level == 0: + os.chdir(os.path.join("..", "..")) + return DirWrap(dirp, *data) + + +@cache_fn +def unpack(dirw: DirWrap): + import shutil + + for k, v in dirw.contains.items(): + if isinstance(v, DirWrap): + if os.path.isdir(k): + shutil.rmtree(k) + os.mkdir(v.name) + os.chdir(v.name) + unpack(v) + os.chdir("..") + else: + open(k, "wb+").write(v.data) diff --git a/.sing/branches/foo/lib/keyexchange.py b/.sing/branches/foo/lib/keyexchange.py new file mode 100644 index 0000000..72097d3 --- /dev/null +++ b/.sing/branches/foo/lib/keyexchange.py @@ -0,0 +1,20 @@ +from cryptography.fernet import Fernet +from random_art.randomart import drunkenwalk, draw +from random import choices +from string import ascii_letters, digits +import os +from .abc import Key + + +def generate_keys(): + key = Fernet.generate_key() + path = os.path.join(".sing", "system", ".keyfile") + hash = "".join(choices(ascii_letters + digits, k=10)) + randomart = draw(drunkenwalk(key), hash) + print(f"The Key is {key}") + print("The Key's randomart is") + print(randomart) + key = Key(path, key, hash, randomart) + key.dump() + print(f"Saved Keys in {path}") + return key diff --git a/.sing/branches/foo/sing.py b/.sing/branches/foo/sing.py new file mode 100644 index 0000000..512db0a --- /dev/null +++ b/.sing/branches/foo/sing.py @@ -0,0 +1,419 @@ +import typer +import os +import typing as t +import shutil +from pathlib import Path +import cson +import getpass +from lib import abc +import string +from lib.db import Database +from lib.dirstat import wrap_directory, unpack + +import socket + +from hooks.remote import remote, clone as _clone +from hooks.diff import diff + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + +app = typer.Typer(name="sing", help="Singularity - the worst Source Control ever") +app.add_typer(remote, name="remote") +app.add_typer(diff, name="diff") + + +@app.command() +def clone(url: str = typer.Argument(...)): + _clone(url, init_fn=init, pull_fn=pull) + + +@app.command() +def init( + dir=typer.Argument("."), + branch: str = typer.Option("master", "-b", "--initial-branch"), + force: bool = typer.Option(False, "-f", "--force", "-r", "--reinit"), +): + """ + Initializes a new Repository, or reinitializes an existing one + """ + initial_cfg = {"current_branch": branch, "branches": [branch], "remotes": {}} + user_cfg = { + "user.name": f"", + "user.email": f"", + "repo.name": os.path.basename(os.path.abspath(dir)), + } + if os.path.exists(os.path.join(dir, ".sing")): + if force: + shutil.rmtree(os.path.join(dir, ".sing")) + print("Reinitializing Singularity Repository...") + else: + return print("Singularity Config Directory already exists - Quitting") + os.mkdir(os.path.join(dir, ".sing")) + os.chdir(os.path.join(dir, ".sing")) + os.mkdir("branches") # Branch Directories + os.mkdir(os.path.join("branches", branch)) # Initial Branch + os.mkdir("stage") # Staged Changes + os.mkdir("system") # Remotes, Branch Config, Local Config, Database + n = open(os.path.join("system", "sing.db"), "wb+") + n.close() + cson.dump(initial_cfg, open(os.path.join("system", "branchcf.cson"), "w+")) + cson.dump(user_cfg, open(os.path.join("system", "localconfig.cson"), "w+")) + os.mkdir("control") # Timeline etc. + os.mkdir("overhead") # Stashed Data + print("Initialized barebones Repository in {0}".format(os.path.abspath(dir))) + + +@app.command() +def config( + key=typer.Argument(None), + list: bool = typer.Option(False, "-l", "--list"), + set: str = typer.Option(None, "--set"), +): + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if key: + if set: + ucfg[key] = set + cson.dump( + ucfg, open(os.path.join(".sing", "system", "localconfig.cson"), "w+") + ) + else: + print(ucfg.get(key, f"Not found: {key}")) + if list: + subpart = {} + for k, v in ucfg.items(): + root, val = k.split(".") + if root not in subpart: + subpart[root] = [] + subpart[root].append((val, v)) + for root, values in subpart.items(): + print(f"- {root}") + for key, value in values: + print(f"---- {key} -> {value}") + + +@app.command() +def log(): + """""" + for commitfile in os.listdir(os.path.join(".sing", "control")): + print(open(os.path.join(".sing", "control", commitfile)).read()) + + +@app.command() +def stash(files: t.List[Path]): + """ + Stashes Files into Overhead to avoid conflicts. Usually called automatically + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "overhead", bn)): + shutil.rmtree(os.path.join(".sing", "overhead", bn)) + shutil.copytree(fp, os.path.join(".sing", "overhead", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "overhead", bn)): + os.remove(os.path.join(".sing", "overhead", bn)) + shutil.copyfile(fp, os.path.join(".sing", "overhead", bn)) + + +@app.command() +def add(files: t.List[Path]): + """ + Stage Files or Directories for a commit + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "stage", bn)): + shutil.rmtree(os.path.join(".sing", "stage", bn)) + shutil.copytree(fp, os.path.join(".sing", "stage", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "stage", bn)): + os.remove(os.path.join(".sing", "stage", bn)) + shutil.copyfile(fp, os.path.join(".sing", "stage", bn)) + + +@app.command() +def rm(files: t.List[str]): + """ + Unstage staged Files + """ + for file in files: + if os.path.exists(os.path.join(".sing", "stage", file)): + if os.path.isdir(os.path.join(".sing", "stage", file)): + shutil.rmtree(os.path.join(".sing", "stage", file)) + else: + os.remove(os.path.join(".sing", "stage", file)) + + +@app.command() +def branch(make_new: str = typer.Option(None, "-m", "--new")): + """ + List Branches or make a new one. To switch, use the 'checkout' command. + """ + if not make_new: + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + for branch in cfg["branches"]: + if branch == cfg["current_branch"]: + print(f"* {branch}") + else: + print(branch) + else: + os.mkdir(os.path.join(".sing", "branches", make_new)) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + cfg["branches"].append(make_new) + cfg["current_branch"] = make_new + cson.dump(cfg, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@app.command() +def commit( + message: str = typer.Option(..., "-m", "--message"), + amend: bool = typer.Option(False, "-a", "--amend"), +): + """ + Commit the staged Files and write them to the Database + + Options: + -m, --message : The Commit Message. + -a. --amend : Overwrite the last commit. + """ + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if ucfg["user.name"] == "" or ucfg["user.email"] == "": + print("*** Please tell me who you are") + print("\nRun\n") + print('\tsing config user.name --set "Your Name" ') + print('\tsing config user.example --set "you@example.com" ') + return + if amend: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))-1}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + else: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + +@app.command() +def stat(): + """ + Print the entire sing Configuration Tree + """ + dw = wrap_directory(".sing") + print(dw.stringify(), end="") + print("local{HEAD}") + + +@app.command() +def slog(): + """ + List all Commits in the Database + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + for k, v in db.data.items(): + print(f"{k} --> {type(v)}") + + +@app.command() +def pull(): + """ + Stash the current Tree and integrate changes downloaded from Remote into active branch + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + stash([Path(".")]) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, id, branch = i.split() + if branch == cfg["current_branch"]: + break + revert(id, branch=branch) + + +@app.command(no_args_is_help=True) +def comtree(c_id: str = typer.Argument(...)): + """ + View the Tree Structure of the Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + print(entry.stringify()) + print("HEAD/") + + +@app.command(no_args_is_help=True) +def revert( + c_id: str = typer.Argument(...), branch: str = typer.Option(None, "-b", "--branch") +): + """ + Reverts to Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +@app.command(no_args_is_help=True) +def checkout( + branch: str = typer.Argument(...), + force: bool = typer.Option(False, "-f", "--force"), +): + """ + Switch branches or restore working tree files + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, c_id, _branch = i.split() + if branch == _branch: + break + + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + if not force: + stash([Path(".")]) + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +if __name__ == "__main__": + app() diff --git a/.sing/branches/foo/test.py b/.sing/branches/foo/test.py new file mode 100644 index 0000000..929db5c --- /dev/null +++ b/.sing/branches/foo/test.py @@ -0,0 +1 @@ +# Lol diff --git a/.sing/branches/master/.signore b/.sing/branches/master/.signore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.sing/branches/master/.signore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/.sing/branches/master/hooks/__pycache__/diff.cpython-311.pyc b/.sing/branches/master/hooks/__pycache__/diff.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdbcd0ef2f886fea871c6f341210bfcc224248ea GIT binary patch literal 2661 zcmc&$TWr%-7(TwmNm>U=+r$kabp`84ODH!9ZK6nQtldK?RG=|}5HhjTI8E%xc3}-E z3aAojsU+p60uFFU5=5#q529aE^;_WaE>P!)PTK8F@z&| zR}AHxb40Uk#LXdRLCE#1r8sWjoDhqJ5Ca$46n`#*hLwyf99H6NGAUg%$Ql!EqzH{% zuv>v%eTCcLkx670QQ<w&Dk68PzI9!W%;H~yN1OH(0rT(G zw0~6BA%jF?2U=v(s?rc2%m<#h=df3)IlHZ+Y{oEUTW><(Y=Pjx);|BI%x%z{GIzDe z{LiKxa&N7bN9NnMK6}l#=p?vw;q6Y&e_bKPTRTfTnsBvkjcfi>uKg^qdWg!Lb~BJZ zlu~I{=9v>wRk+GC=OuYc5GR?Fg3L!%NuFU?kz9hr)8NJ)yvgo!&#kq1wOJ& z1|J+8gkv~t@x#NzOa>if#zP(hl@tR{vufOM2ue&yLjU|{Dlc*dE~R4bef7}?4@V!5Js2zbmi8{6)&gUCU`+F! z(tW2iYFwwrb0cL>*Gl(0x~D%kS|&YfpqpSS9i0pQ4VDATlUiUz4~%FXqk6}v2|TWW zHEXzYqSD#DaCGtbz2n6`t#gmwxyJ+)HBg#Z>Mva^U94>H&c_P}^YMKA?zV+(dDve1 z=)y?;dj5LFw@df!t)}5fpeI;J6ekLaLPDcMP-(^fRC;>#o`KeW{gz+=AtMsln z!u@Sm>TX~mU;@I$*u$L@t6koO(Z%t51ZO?!lw5$VgcVwX zcHmavW^gW8ruP(xhwa64r6Wr_HF{X5hfNSfAWk^syViM|Tf+dleJ*ICbq#yfPW&HD C#3xk% literal 0 HcmV?d00001 diff --git a/.sing/branches/master/hooks/__pycache__/remote.cpython-311.pyc b/.sing/branches/master/hooks/__pycache__/remote.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03ad8e31248ef8a4640ab8cec9c4349fdb11f721 GIT binary patch literal 16405 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 literal 0 HcmV?d00001 diff --git a/.sing/branches/master/hooks/diff.py b/.sing/branches/master/hooks/diff.py new file mode 100644 index 0000000..891c3c1 --- /dev/null +++ b/.sing/branches/master/hooks/diff.py @@ -0,0 +1,41 @@ +import typer +import cson +import os +import sys +import shutil + +diff = typer.Typer(name="diff") + + +@diff.command() +def cmp(shallow: bool = typer.Option(True, "-s", "--shallow")): + """ + Compare Active Working Directory and latest commit on the same branch + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + masterb = config["current_branch"] + if shallow: + for i in os.listdir(os.path.join(".sing", "branches", masterb)): + if not os.path.exists(i) and not i in ignore: + print(f"- {i}") + else: + if not i in ignore: + if shutil.disk_usage( + os.path.join(".sing", "branches", masterb, i) + ) > shutil.disk_usage(i): + print(f"+++ {i}") + elif shutil.disk_usage( + os.path.join(".sing", "branches", masterb, i) + ) < shutil.disk_usage(i): + print(f"--- {i}") + else: + print(f"=== {i}") + for i in os.listdir(): + if ( + not os.path.exists(os.path.join(".sing", "branches", masterb, i)) + and not i in ignore + ): + print(f"+ {i}") diff --git a/.sing/branches/master/hooks/remote.py b/.sing/branches/master/hooks/remote.py new file mode 100644 index 0000000..5594708 --- /dev/null +++ b/.sing/branches/master/hooks/remote.py @@ -0,0 +1,234 @@ +import typer +import socket +import cson +import os +import sys +import pickle +from lib.db import Database +from lib.abc import FileWrap, Key +from lib.keyexchange import generate_keys + + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + + +remote = typer.Typer(name="remote") + +cstep = 0 + +CHUNK_SIZE = 1 + + +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i : i + n] + + +def cycle(): + global cstep + steps = ["/", "-", "\\", "|"] + cstep += 1 + if cstep == 4: + cstep = 0 + return steps[cstep] + + +@remote.command() +def add(name: str, url: str): + """ + Add a remote named for the repository at . The command 'sing remote fetch' can then + be used to create and update remote branches (/) + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["remotes"][name] = url + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@remote.command(name="keys") +def kg( + generate: bool = typer.Option(True, "-g", "--generate-new"), + publish: bool = typer.Option(False, "-p", "--publish"), +): + if generate: + generate_keys() + print("REMINDER : THIS KEY WILL BE USED TO ENCRYPT REMOTE PUSHES.") + print("Other Clients will not be able to decrypt the Push unless they") + print("receive the key. Use these commands to share your keys:") + print("\tsing remote keys --publish") + + if publish: + if not os.path.exists(os.path.join(".sing", "system", ".keyfile")): + return print( + "Fatal -- no Keys found. Use the '-g' option to create new keys" + ) + print("Importing Keys...") + key: Key = Key.kimport(os.path.join(".sing", "system", ".keyfile")) + print(key.randomart) + import getpass + + print("Do you want to protect your Keys using a Passphrase?") + print("Recipients will be prompted for their Password before getting the Key") + p = input("[y/N]") or "n" + p = p.lower() + if p in ["y", "yes"]: + passphrase = getpass.getpass("Enter Passphrase : ") + pass_rep = getpass.getpass("Re-Type Passphrase:") + i = 1 + while not pass_rep == passphrase and i < 3: + pass_rep = getpass.getpass( + "Wrong Passphrase - Please re-type Passphrase:" + ) + i += 1 + if i >= 3: + return print("Aborted - 3 Times entered Wrong Password") + + +@remote.command(name="get-url") +def gurl(remote_name): + """ + Retrieves the URLs for a Remote. + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + rmurl = config["remotes"].get(remote_name, "Fatal -- No Remote Found.") + print(rmurl) + + +def clone(url, init_fn, pull_fn): + """ + Download objects from remote repository + """ + import urllib.parse + + parsed = urllib.parse.urlparse(url) + if os.path.exists(parsed.path.split("/")[-1]): + return print(f"Fatal -- File/Directory exists: {parsed.path.split('/')[-1]}") + print("Connecting to remote Server...") + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(parsed.hostname), 2991)) + s = f"down {parsed.path}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {parsed.path}".encode()) + + database = [] + print("Initializing Repository...") + os.mkdir(parsed.path.split("/")[-1]) + os.chdir(parsed.path.split("/")[-1]) + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + init_fn(".", Branch_Config["current_branch"], True) + os.chdir("..") + try: + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + except: + config = {} + config = Branch_Config + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{url}/HEAD -> local/HEAD") + print("Pulling changes...") + pull_fn() + + +@remote.command() +def fetch(remote_name): + """ + Download objects from remote repository + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"down {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {lconfig['repo.name']}".encode()) + + database = [] + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["branches"] = Branch_Config["branches"] + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{remote_name}/HEAD -> local/HEAD") + print("Use 'sing pull' to write into local files") + + +@remote.command() +def push(remote_name): + """ + Update remote refs along with associated objects + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"up {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(8) + sock.send(s.encode()) + db = { + "MainDB": open(os.path.join(".sing", "system", "sing.db"), "rb").read(), + "Branches": config, + "CommitHistory": [ + FileWrap(i, open(os.path.join(".sing", "control", i), "rb").read()) + for i in os.listdir(os.path.join(".sing", "control")) + ], + } + db = pickle.dumps(db) + c = list(chunks(db, CHUNK_SIZE)) + for i, chunk in enumerate(c): + print(f"Sending Objects... {i}/{len(c)-1} {cycle()}", end="\r") + sock.send(chunk) + print() + print(f"Origin -> HEAD[{remote_name}]") diff --git a/.sing/branches/master/lib/__pycache__/abc.cpython-311.pyc b/.sing/branches/master/lib/__pycache__/abc.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e645cbfdf361fc2e841e99d1447c6d04e722f643 GIT binary patch literal 6003 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 literal 0 HcmV?d00001 diff --git a/.sing/branches/master/lib/__pycache__/db.cpython-311.pyc b/.sing/branches/master/lib/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e6cf677e15ddb8e3cd11d2bab31f972ed610c98 GIT binary patch literal 2594 zcmb_d&r2g$9Di?qWhN#@-Q8BBxNhyTjn>4n6S96CB}xSH z88M_~V#qtN%N{&l(`AFENKAQ&!>N}PFebX{%C+Ds6u({3Ir!d|m zCef)ubivRJaaqzu{D-<^$bhmDG?ZnkD@FiD0aG>9WwHaS_Wb>H%@81!4p)7YaWdJA zZMq6GoiZy<<(mHpCoAxX@SObyut}=4O449?O(PD*8m&qV+Wx*1WKnSBW}J{&e|A;a zfw$j726%&AoBSfP(A#&S;7O5T@&U1>dEpyzn#@y5rU`}aMJUOW)?KE#-yPP$jcqd96zE^T8wArBgA=x?mI&SvUN+;&XGsmCd5V7Mg^! z8yJx1foikT>C?)`oe;8dA#mC^2%Ku`!>gSu(9gh0a=y4=;LCUe2Ajlbi9vS$=n4VT zYXnT+tbkbuBo>od7=F0?7``5hAaITZ&JpVYhm9ZT(2&QCQl10pnw5x8j^{vP>~8^< z$Vs?+{oRdwTN8)jJ4fL=aPr!CO+V=!-0rA7KI}~#^(OHAL224J4{`vdY2%=@R^LVR zlI1DwKc?FFCNH9&h`&oo3Ob^IZK{zV&Qv3mhPSdklR3*Rq(x44z6-8+no z97RSB)DectzFJ=|Ao51^MP`u^-l~_%Dwe$eUm{~z1n02`UJ@D9zH(u%Rb{T$D1G^2 zyVi20LLu+8Xw7pVSN1Bvm0F9}rcXjWYfrYMZLRk4VQB0qGxl*xc=A25GFQA5J06D=gcZvKN=~=PYCsr$Kl>@bpiw88SL2SwsY!L4b+&i6i zgXwgkWRxwmL+SL_<&5P;*iBf#`Veq$u3I(?mfMlDGPYeXov%s;dkr)Yz!W$QA5@}r*99s^abEOPgyHfR z7Y^?A62!D(3a@sqn91gt1P+FYGA!CW?Mv_TT7BSA^pM423RVaEO8`(5rN<<^#QQNB XJ-Gg_uJlnz?%!~VzE@WSc)I@p0iq6O literal 0 HcmV?d00001 diff --git a/.sing/branches/master/lib/__pycache__/dirstat.cpython-311.pyc b/.sing/branches/master/lib/__pycache__/dirstat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0ac1e6b68e84f3e627962801d6addffb9601a26 GIT binary patch literal 2998 zcma(T-)q}O_)e04%dwm$OH~QdZpm$9v!1jEQHAcV0K~R4LaP-z0FnoDQH*=wQ^khj#>?OB-7X&)QPGe&h zgXUf_r%s7Ji&;^zj~3+wvOLLzs*wekk+en2l~4&1%1B66ZsDa8*H+NF{_BE^ETO3WUOMqdq#r_$Q^)kJz>H7TKlwmzOr%#Gs> zswQctGV6#~f&yZ8^&SQ}urGAxW~!Wfb2@+Ro7iS77pn$Ch1sneg&Q@%xlU7%ht)u^ zaDHcUdvY&q1R`c2QUk2-bTu;i=!_8=Gb3a9nWD6HIe(e#zP@vF`{v$7BiL^S`)dFa z!GRJiP28KjKlv;&_B1kPM8?g?czx494)v8lSDEX2i7yA@jnKmgaTE>b5;pv{(`I_U#(A9$OaQ z4=g~P{Skf!z5_V;xfmiBLByhnC=z)Zc{toPC8b*agqld>xJ<`!gv*JzX1S#d4s_Y- zSi@DVnFomY9FYqm619k!#Nw7#5@|$=jOa#32*s6DZYxRDMY*!1NfunT;BA%o5a$LS z=qO&IcWls$7wWGVa&W);Ch&g`G>=mwxCdefenkm%+!P(K})GP8j?-lRsCc@BV>^w-hd2 zyw`ER!{ASv{K+zXD;~ZGzum}fRK5O!_xsMfJ-dB(`u60~lo1xquxNNkP48&gJ6a8N z7AMMqfxUMzIC!}MMBG3t!`k5uz@ix=Lm4D-6VO4k^p|L~;O&m_T88{iGla>qnx`qK zvsz2>bVdr=)>>k!*(pmc`&&+GE1;-bhldyD_VSKfRZ9zLMzqc@HT1XVn`1{~_gi-y zYq=}?|IT~8DHI-Pk(P7t{|u+iN-eqBBF8Q@8d$sL!XNTMt~JtZgI$|@Ulc62y11$( zl9o5VlGY@gYKVLTI!*w|n|el(WI{x(V1@>8cL@$wgrmE`JHb845Td3K zEep{q-%*$@Ub*|Z!S|VbUzzVajM7;=vo%wg$v)Iw$fLE6v606{}%n%QwH8#w~p%sal>zNtHAWAySsG zZ^(bPTk~nlmqe?#C|2*$v`P`PJqA<(tzECY7g8(oYEt8e$GA{m6d-DVaS= literal 0 HcmV?d00001 diff --git a/.sing/branches/master/lib/__pycache__/keyexchange.cpython-311.pyc b/.sing/branches/master/lib/__pycache__/keyexchange.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a7432b76db8e34a1c5bbf732103fc065ae26238 GIT binary patch literal 1501 zcmaJ>-)q};5dTX0TDGMq%VuX=TbjBK-6kjw8zX5)wwILAWzdIZgP}bH)%w;}BFW~f zZ`Rq!ppC-X($T(@8b-lS*@idRpCW-CguqXI%3C0BecDN>f3;!f)A{bccjx=w=j1;W zMM6OB2Q~5?kI;+M7!%kToqP{p&yax(&O$b)aa^kN7HP zY*76!9OvuJmRTn*oGZFpH_cs(Fh;1Gh=$oPnS1MqJ1r!9O*-|o=$J%M1b9vohy=i@q^N`0w49spgnO_fiXfSj8Y5Y7Auk}&%xU+io5Ui4PtDCF2rqf77x8pKm zcMG+B(z#<=Btb{%qQf3>`(63lCaJ)ZR7|(hefO2|pAi}&d4rQPWYCa;I_`13Epd&4ox?W=2KBfMJ^TTjgwrPnhv9B53tb( zc3Z`X02SyvnWw3{&7?T_eK*ai0KHsYvmLg!19v*GbZWBBnq}TzgGBOEy{S75Qfqe- zWws}mo`>WjJZ=cWM83+44lUiWXrvhsN2 z;l|UA=b!(x9$xtP-*1A_wXk$80x4gQ*8(Ng*84}(&;d?i}GGGO7-)oAIe|J4W= z1L&;(o{=9u}+uzQTFZVwUq|3k|u12^T;A$){`r9XPDWonYr8@1jS*ty->zq9Wz+l%(F@?E=RmwHY}zg3 zz#?DJDwyfp=>7)cSl~D=Mjr(89HUQyvp7cQgL#h8N5NSfqoSXEVpI<1`B;>>w~o=9 HB5U_Qp`uTI literal 0 HcmV?d00001 diff --git a/.sing/branches/master/lib/abc.py b/.sing/branches/master/lib/abc.py new file mode 100644 index 0000000..caf3860 --- /dev/null +++ b/.sing/branches/master/lib/abc.py @@ -0,0 +1,94 @@ +import typing as t + + +#################### +# Directory Wrappers +# + + +class DirWrap: + def __init__(self, fname, *data: t.List[t.Union["DirWrap", "FileWrap"]]): + self.name = fname + self.contains = {d.name: d for d in data} + self._raw_data = data + + def stringify(self, level=0) -> str: + s = "" + for key, value in self.contains.items(): + s += ( + "├" + + "─" + + "─" * (4 * level) + + " " + + key + + ("/" if isinstance(value, DirWrap) else "") + + "\n" + ) + if isinstance(value, DirWrap): + s += value.stringify(level + 1) + return s + + +class FileWrap: + def __init__(self, fname: str, fdata: t.Union[bytes, str]) -> None: + self.name = fname + self.data = fdata.encode() if isinstance(fdata, str) else fdata + + +######### +# Streams +# + + +class IStream: + def __init__(self, file) -> None: + self.file = file + + def write(self, *data): + self.file.write(*data) + + +class IOStream: + def __init__(self, fdin: IStream, fdout: "OStream") -> None: + self.fdin = fdin + self.fdout = fdout + + def write(self, *data): + self.fdin.write(*data) + + def read(self): + return self.fdout.read() + + +class OStream: + def __init__(self, file) -> None: + self.file = file + + def read(self): + return self.file.read() + + +class Key: + def __init__(self, kpath, key, hash, randomart): + self.kp = kpath + self.key = key + self.hash = hash + self.randomart = randomart + + def dump(self): + open(self.kp, "wb+").write( + "--- BEGIN FERNET CRYPTOGRAPHY KEY ---\n".encode() + + self.key + + f"\n{self.hash}\n".encode() + + "\n--- END FERNET CRYPTOGRAPHY KEY ---".encode() + ) + + @classmethod + def kimport(cls, kp): + k = open(kp, "rb").read().splitlines() + key = k[1] + hash = k[2].decode() + from random_art.randomart import drunkenwalk, draw + + randomart = draw(drunkenwalk(key), hash) + return cls(kp, key, hash, randomart) diff --git a/.sing/branches/master/lib/db.py b/.sing/branches/master/lib/db.py new file mode 100644 index 0000000..ee68c23 --- /dev/null +++ b/.sing/branches/master/lib/db.py @@ -0,0 +1,30 @@ +import pickle +from .abc import IOStream, IStream, OStream + + +class Database: + def __init__(self, fn) -> None: + self.fn = fn + try: + with open(fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + except: + self.data = {} + + def write(self, key, entry): + self.data[key] = entry + + def update(self): + with open(self.fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + + def get(self, key, default=None): + return self.data.get(key, default) + + def commit(self): + with open(self.fn, "wb+") as stream_in: + pickle.dump(self.data, stream_in) + + @classmethod + def connect(cls, fname): + return cls(fname) diff --git a/.sing/branches/master/lib/dirstat.py b/.sing/branches/master/lib/dirstat.py new file mode 100644 index 0000000..bf4f84d --- /dev/null +++ b/.sing/branches/master/lib/dirstat.py @@ -0,0 +1,51 @@ +from .abc import FileWrap, DirWrap +import os +import functools + +if hasattr(functools, "cache"): + cache_fn = functools.cache +else: + cache_fn = functools.lru_cache + + +@cache_fn +def parse_directory(dirp): + structure = {} + os.chdir(dirp) + for d in os.listdir(): + if os.path.isdir(d): + structure[d] = parse_directory(d) + elif os.path.isfile(d): + structure[d] = open(d, "rb").read() + os.chdir("..") + return structure + + +@cache_fn +def wrap_directory(dirp, level=0): + structure = parse_directory(dirp) + data = [] + for k, v in structure.items(): + if isinstance(v, dict): + data.append(wrap_directory(k, level + 1)) + else: + data.append(FileWrap(k, v)) + if level == 0: + os.chdir(os.path.join("..", "..")) + return DirWrap(dirp, *data) + + +@cache_fn +def unpack(dirw: DirWrap): + import shutil + + for k, v in dirw.contains.items(): + if isinstance(v, DirWrap): + if os.path.isdir(k): + shutil.rmtree(k) + os.mkdir(v.name) + os.chdir(v.name) + unpack(v) + os.chdir("..") + else: + open(k, "wb+").write(v.data) diff --git a/.sing/branches/master/lib/keyexchange.py b/.sing/branches/master/lib/keyexchange.py new file mode 100644 index 0000000..72097d3 --- /dev/null +++ b/.sing/branches/master/lib/keyexchange.py @@ -0,0 +1,20 @@ +from cryptography.fernet import Fernet +from random_art.randomart import drunkenwalk, draw +from random import choices +from string import ascii_letters, digits +import os +from .abc import Key + + +def generate_keys(): + key = Fernet.generate_key() + path = os.path.join(".sing", "system", ".keyfile") + hash = "".join(choices(ascii_letters + digits, k=10)) + randomart = draw(drunkenwalk(key), hash) + print(f"The Key is {key}") + print("The Key's randomart is") + print(randomart) + key = Key(path, key, hash, randomart) + key.dump() + print(f"Saved Keys in {path}") + return key diff --git a/.sing/branches/master/sing.py b/.sing/branches/master/sing.py new file mode 100644 index 0000000..1a42b00 --- /dev/null +++ b/.sing/branches/master/sing.py @@ -0,0 +1,419 @@ +import typer +import os +import typing as t +import shutil +from pathlib import Path +import cson +import getpass +from lib import abc +import string +from lib.db import Database +from lib.dirstat import wrap_directory, unpack + +import socket + +from hooks.remote import remote, clone as _clone +from hooks.diff import diff + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + +app = typer.Typer(name="sing", help="Singularity - the worst Source Control ever") +app.add_typer(remote, name="remote") +app.add_typer(diff, name="diff") + + +@app.command() +def clone(url: str = typer.Argument(...)): + _clone(url, init_fn=init, pull_fn=pull) + + +@app.command() +def init( + dir=typer.Argument("."), + branch: str = typer.Option("master", "-b", "--initial-branch"), + force: bool = typer.Option(False, "-f", "--force", "-r", "--reinit"), +): + """ + Initializes a new Repository, or reinitializes an existing one + """ + initial_cfg = {"current_branch": branch, "branches": [branch], "remotes": {}} + user_cfg = { + "user.name": f"", + "user.email": f"", + "repo.name": os.path.basename(os.path.abspath(dir)), + } + if os.path.exists(os.path.join(dir, ".sing")): + if force: + shutil.rmtree(os.path.join(dir, ".sing")) + print("Reinitializing Singularity Repository...") + else: + return print("Singularity Config Directory already exists - Quitting") + os.mkdir(os.path.join(dir, ".sing")) + os.chdir(os.path.join(dir, ".sing")) + os.mkdir("branches") # Branch Directories + os.mkdir(os.path.join("branches", branch)) # Initial Branch + os.mkdir("stage") # Staged Changes + os.mkdir("system") # Remotes, Branch Config, Local Config, Database + n = open(os.path.join("system", "sing.db"), "wb+") + n.close() + cson.dump(initial_cfg, open(os.path.join("system", "branchcf.cson"), "w+")) + cson.dump(user_cfg, open(os.path.join("system", "localconfig.cson"), "w+")) + os.mkdir("control") # Timeline etc. + os.mkdir("overhead") # Stashed Data + print("Initialized barebones Repository in {0}".format(os.path.abspath(dir))) + + +@app.command() +def config( + key=typer.Argument(None), + list: bool = typer.Option(False, "-l", "--list"), + set: str = typer.Option(None, "--set"), +): + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if key: + if set: + ucfg[key] = set + cson.dump( + ucfg, open(os.path.join(".sing", "system", "localconfig.cson"), "w+") + ) + else: + print(ucfg.get(key, f"Not found: {key}")) + if list: + subpart = {} + for k, v in ucfg.items(): + root, val = k.split(".") + if root not in subpart: + subpart[root] = [] + subpart[root].append((val, v)) + for root, values in subpart.items(): + print(f"- {root}") + for key, value in values: + print(f"---- {key} -> {value}") + + +@app.command() +def log(): + """""" + for commitfile in os.listdir(os.path.join(".sing", "control")): + print(open(os.path.join(".sing", "control", commitfile)).read()) + + +@app.command() +def stash(files: t.List[Path]): + """ + Stashes Files into Overhead to avoid conflicts. Usually called automatically + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "overhead", bn)): + shutil.rmtree(os.path.join(".sing", "overhead", bn)) + shutil.copytree(fp, os.path.join(".sing", "overhead", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "overhead", bn)): + os.remove(os.path.join(".sing", "overhead", bn)) + shutil.copyfile(fp, os.path.join(".sing", "overhead", bn)) + + +@app.command() +def add(files: t.List[Path]): + """ + Stage Files or Directories for a commit + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "stage", bn)): + shutil.rmtree(os.path.join(".sing", "stage", bn)) + shutil.copytree(fp, os.path.join(".sing", "stage", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "stage", bn)): + os.remove(os.path.join(".sing", "stage", bn)) + shutil.copyfile(fp, os.path.join(".sing", "stage", bn)) + + +@app.command() +def rm(files: t.List[str]): + """ + Unstage staged Files + """ + for file in files: + if os.path.exists(os.path.join(".sing", "stage", file)): + if os.path.isdir(os.path.join(".sing", "stage", file)): + shutil.rmtree(os.path.join(".sing", "stage", file)) + else: + os.remove(os.path.join(".sing", "stage", file)) + + +@app.command() +def branch(make_new: str = typer.Option(None, "-m", "--new")): + """ + List Branches or make a new one. To switch, use the 'checkout' command. + """ + if not make_new: + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + for branch in cfg["branches"]: + if branch == cfg["current_branch"]: + print(f"* {branch}") + else: + print(branch) + else: + os.mkdir(os.path.join(".sing", "branches", make_new)) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + cfg["branches"].append(make_new) + cfg["current_branch"] = make_new + cson.dump(cfg, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@app.command() +def commit( + message: str = typer.Option(..., "-m", "--message"), + amend: bool = typer.Option(False, "-a", "--amend"), +): + """ + Commit the staged Files and write them to the Database + + Options: + -m, --message : The Commit Message. + -a. --amend : Overwrite the last commit. + """ + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if ucfg["user.name"] == "" or ucfg["user.email"] == "": + print("*** Please tell me who you are") + print("\nRun\n") + print('\tsing config user.name --set "Your Name" ') + print('\tsing config user.example --set "you@example.com" ') + return + if amend: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))-1}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + else: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + +@app.command() +def stat(): + """ + Print the entire sing Configuration Tree + """ + dw = wrap_directory(".sing") + print(dw.stringify(), end="") + print("local{HEAD}") + + +@app.command() +def slog(): + """ + List all Commits in the Database + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + for k, v in db.data.items(): + print(f"{k} --> {type(v)}") + + +@app.command() +def pull(): + """ + Stash the current Tree and integrate changes downloaded from Remote into active branch + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + stash([Path(".")]) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, id, branch = i.split() + if branch == cfg["current_branch"]: + break + revert(id, branch=branch) + + +@app.command(no_args_is_help=True) +def comtree(c_id: str = typer.Argument(...)): + """ + View the Tree Structure of the Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + print(entry.stringify()) + print("HEAD/") + + +@app.command(no_args_is_help=True) +def revert( + c_id: str = typer.Argument(...), branch: str = typer.Option(None, "-b", "--branch") +): + """ + Reverts to Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +@app.command(no_args_is_help=True) +def checkout( + branch: str = typer.Option(None, "-b", "--branch"), + force: bool = typer.Option(False, "-f", "--force"), +): + """ + Switch branches or restore working tree files + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, c_id, _branch = i.split() + if branch == _branch: + break + + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + if not force: + stash([Path(".")]) + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +if __name__ == "__main__": + app() diff --git a/.sing/branches/master/test.py b/.sing/branches/master/test.py new file mode 100644 index 0000000..929db5c --- /dev/null +++ b/.sing/branches/master/test.py @@ -0,0 +1 @@ +# Lol diff --git a/.sing/control/COMMIT-0.commit b/.sing/control/COMMIT-0.commit new file mode 100644 index 0000000..4f3a3ed --- /dev/null +++ b/.sing/control/COMMIT-0.commit @@ -0,0 +1,8 @@ + + commit Td0HEjzAM8FuvnYUOnOTP6p2KTz7v9Ig master + + Author: a + Date: 2023-10-16 08:00:22.388514 + + feat: Initial Commit + \ No newline at end of file diff --git a/.sing/control/COMMIT-1.commit b/.sing/control/COMMIT-1.commit new file mode 100644 index 0000000..5cde65b --- /dev/null +++ b/.sing/control/COMMIT-1.commit @@ -0,0 +1,8 @@ + + commit pgVDG99ljSQxDWI4GZm5aI0ahtDXcmbU secondary + + Author: Secondary Jane + Date: 2023-10-16 14:27:16.538311 + + kewl + \ No newline at end of file diff --git a/.sing/control/COMMIT-2.commit b/.sing/control/COMMIT-2.commit new file mode 100644 index 0000000..f359094 --- /dev/null +++ b/.sing/control/COMMIT-2.commit @@ -0,0 +1,8 @@ + + commit icG9CADYZ33JNERREpa2fpGYTRA3G93d master + + Author: Jane Johanna Yosef + Date: 2023-10-16 14:36:34.801126 + + Commit + \ No newline at end of file diff --git a/.sing/control/COMMIT-3.commit b/.sing/control/COMMIT-3.commit new file mode 100644 index 0000000..8efb6d0 --- /dev/null +++ b/.sing/control/COMMIT-3.commit @@ -0,0 +1,8 @@ + + commit zCzv53Xfm4O3gZrz31vj2br4tmWc2mlv master + + Author: Jane Johanna Yosef + Date: 2023-10-24 16:10:42.542050 + + blob + \ No newline at end of file diff --git a/.sing/control/COMMIT-4.commit b/.sing/control/COMMIT-4.commit new file mode 100644 index 0000000..016427a --- /dev/null +++ b/.sing/control/COMMIT-4.commit @@ -0,0 +1,8 @@ + + commit x1JPywLQQrB3yYv5Qrd2a4Dg3DFWl5kd master + + Author: Jane Johanna Yosef + Date: 2023-10-25 07:48:27.216594 + + feat: Add checkout + \ No newline at end of file diff --git a/.sing/control/COMMIT-5.commit b/.sing/control/COMMIT-5.commit new file mode 100644 index 0000000..8e5a401 --- /dev/null +++ b/.sing/control/COMMIT-5.commit @@ -0,0 +1,8 @@ + + commit YmRvFt2tbeNt7sbybxaCyen3hRkz2YwU foo + + Author: Jane Johanna Yosef + Date: 2023-10-25 07:51:09.514969 + + fix: Improve Checkout Command + \ No newline at end of file diff --git a/.sing/control/COMMIT-6.commit b/.sing/control/COMMIT-6.commit new file mode 100644 index 0000000..245c165 --- /dev/null +++ b/.sing/control/COMMIT-6.commit @@ -0,0 +1,8 @@ + + commit E1k6zZKOcU0d3815pEwIYcUdXzf0RtPy foo + + Author: Jane Johanna Yosef + Date: 2023-10-25 07:54:31.541732 + + fix: Forgot to update config + \ No newline at end of file diff --git a/.sing/control/COMMIT-7.commit b/.sing/control/COMMIT-7.commit new file mode 100644 index 0000000..f9164d7 --- /dev/null +++ b/.sing/control/COMMIT-7.commit @@ -0,0 +1,8 @@ + + commit hcfKqMPHotAyoCjDFvRnIyNnFcP2u93U foo + + Author: Jane Johanna Yosef + Date: 2023-10-26 14:00:13.543435 + + None + \ No newline at end of file diff --git a/.sing/control/COMMIT-8.commit b/.sing/control/COMMIT-8.commit new file mode 100644 index 0000000..f67d969 --- /dev/null +++ b/.sing/control/COMMIT-8.commit @@ -0,0 +1,9 @@ + + commit L81bNUlwe0XgNi5IHlPa6Ix1HVRs91eO foo + + Author: Jane Johanna Yosef + Date: 2023-10-26 14:00:41.063254 + + Hello World + + \ No newline at end of file diff --git a/.sing/stage/.signore b/.sing/stage/.signore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.sing/stage/.signore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/.sing/stage/foobar b/.sing/stage/foobar new file mode 100644 index 0000000..e69de29 diff --git a/.sing/stage/hooks/__pycache__/diff.cpython-311.pyc b/.sing/stage/hooks/__pycache__/diff.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0edd7bc77759107a76fce8a63815b06d2e968e6e GIT binary patch literal 2661 zcmc&$OKcNI7@mE3Z6|98N$ia=&PJpuPB7sm(N+YB7LrQ{L_wt(A!NMkcpZD!+TB27 z9Ar==P!*{L1;t9CSXB!~aLA#D969#TL$+F})=HL;svLUTlvZsyb$0zo9O6nymHLlo z=Ksh4>-*6ZW*7gL08n9O=im)W- zh@z~0j%c(EIay=^1RTGbieoy?^3iAj(QzT1UvdTp2DMBL$ z>}H_XU*i^dWD=Q06u3~McwM`I*48%9EZPWLI+{TW_0%_ODrT%IZM7=Y3t7`J=SHxN zaS)(T8@lm&p*Kfqf}>vS4SN7LoyAzZvG9!%8>Lw^gR(?d-Nh`H0Xal^rqt^NK)jCl z9W$|Iuz${zRn;=-)-sk_6xdnPl=8h(r z|JkrZ&h52w$b8H8XRq-VodTCHzT3(9uPY?Gt7mCf1Fn|sam|0mwoU-E2dMN}Cj;pd zF_{WW9CI?F@Yguzf+$V#!X$Hwm$--`N;6DYV3|Z%;beu0h{+_cFrvUHF^-YpyU1LX z!a^iwc}f!pWN4bU!)a0wB~G`=GqS=Z(^N$(5{v*}h zr@8xnb|3u7eNc5D*4&3x`w`84WYvD8OtsBle4sq~_~FRo(WTL%XL;YsS=BeH`9@XG z8O?J>rN%UBEIV9sb*y&2r@8vFBPG(c4mt^@+}6I}-DKInGO7B8HQ%t>HlnqS7{KM| zUpI%lC(G@f3&$2u+&@w1QQLd9_Fe-}RDW@NxvzMsc&WUzGZ)Jr%EfZAdpj0(D7@s;;q9#ZMk8hv^lLuvQ#01j)kU!w!X9+lp& z(ffZRP&#P9_23^i(B;p^3*-5CKCaRMsI+Q*%3a-BSATQAUemAlZNFaAueZ71HG0om z;r_NNbr1A0-ZE3B)p%x?QBZn;myLYiekb#ZO?$@H?4hecw;}F6xrzncS7oeqWPOD zFT%mV5CkcK)~wY^pe1Xy5^!X#Rsznf)k=WMTJ06ND?2@RBX3(JyH&DVBfGP>VFRe` zj_hi!`8z2;?d>ZDm|ppLk9385GNdR9UHvGtz!V)Ip;UfnufJ%CH@b7 CEGRet literal 0 HcmV?d00001 diff --git a/.sing/stage/hooks/__pycache__/remote.cpython-311.pyc b/.sing/stage/hooks/__pycache__/remote.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21d643fd1a348b77b662ad151c30d84e960d8d0f GIT binary patch literal 16405 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 shutil.disk_usage(i): + print(f"+++ {i}") + elif shutil.disk_usage( + os.path.join(".sing", "branches", masterb, i) + ) < shutil.disk_usage(i): + print(f"--- {i}") + else: + print(f"=== {i}") + for i in os.listdir(): + if ( + not os.path.exists(os.path.join(".sing", "branches", masterb, i)) + and not i in ignore + ): + print(f"+ {i}") diff --git a/.sing/stage/hooks/remote.py b/.sing/stage/hooks/remote.py new file mode 100644 index 0000000..5594708 --- /dev/null +++ b/.sing/stage/hooks/remote.py @@ -0,0 +1,234 @@ +import typer +import socket +import cson +import os +import sys +import pickle +from lib.db import Database +from lib.abc import FileWrap, Key +from lib.keyexchange import generate_keys + + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + + +remote = typer.Typer(name="remote") + +cstep = 0 + +CHUNK_SIZE = 1 + + +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i : i + n] + + +def cycle(): + global cstep + steps = ["/", "-", "\\", "|"] + cstep += 1 + if cstep == 4: + cstep = 0 + return steps[cstep] + + +@remote.command() +def add(name: str, url: str): + """ + Add a remote named for the repository at . The command 'sing remote fetch' can then + be used to create and update remote branches (/) + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["remotes"][name] = url + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@remote.command(name="keys") +def kg( + generate: bool = typer.Option(True, "-g", "--generate-new"), + publish: bool = typer.Option(False, "-p", "--publish"), +): + if generate: + generate_keys() + print("REMINDER : THIS KEY WILL BE USED TO ENCRYPT REMOTE PUSHES.") + print("Other Clients will not be able to decrypt the Push unless they") + print("receive the key. Use these commands to share your keys:") + print("\tsing remote keys --publish") + + if publish: + if not os.path.exists(os.path.join(".sing", "system", ".keyfile")): + return print( + "Fatal -- no Keys found. Use the '-g' option to create new keys" + ) + print("Importing Keys...") + key: Key = Key.kimport(os.path.join(".sing", "system", ".keyfile")) + print(key.randomart) + import getpass + + print("Do you want to protect your Keys using a Passphrase?") + print("Recipients will be prompted for their Password before getting the Key") + p = input("[y/N]") or "n" + p = p.lower() + if p in ["y", "yes"]: + passphrase = getpass.getpass("Enter Passphrase : ") + pass_rep = getpass.getpass("Re-Type Passphrase:") + i = 1 + while not pass_rep == passphrase and i < 3: + pass_rep = getpass.getpass( + "Wrong Passphrase - Please re-type Passphrase:" + ) + i += 1 + if i >= 3: + return print("Aborted - 3 Times entered Wrong Password") + + +@remote.command(name="get-url") +def gurl(remote_name): + """ + Retrieves the URLs for a Remote. + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + rmurl = config["remotes"].get(remote_name, "Fatal -- No Remote Found.") + print(rmurl) + + +def clone(url, init_fn, pull_fn): + """ + Download objects from remote repository + """ + import urllib.parse + + parsed = urllib.parse.urlparse(url) + if os.path.exists(parsed.path.split("/")[-1]): + return print(f"Fatal -- File/Directory exists: {parsed.path.split('/')[-1]}") + print("Connecting to remote Server...") + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(parsed.hostname), 2991)) + s = f"down {parsed.path}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {parsed.path}".encode()) + + database = [] + print("Initializing Repository...") + os.mkdir(parsed.path.split("/")[-1]) + os.chdir(parsed.path.split("/")[-1]) + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + init_fn(".", Branch_Config["current_branch"], True) + os.chdir("..") + try: + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + except: + config = {} + config = Branch_Config + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{url}/HEAD -> local/HEAD") + print("Pulling changes...") + pull_fn() + + +@remote.command() +def fetch(remote_name): + """ + Download objects from remote repository + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"down {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {lconfig['repo.name']}".encode()) + + database = [] + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["branches"] = Branch_Config["branches"] + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{remote_name}/HEAD -> local/HEAD") + print("Use 'sing pull' to write into local files") + + +@remote.command() +def push(remote_name): + """ + Update remote refs along with associated objects + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"up {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(8) + sock.send(s.encode()) + db = { + "MainDB": open(os.path.join(".sing", "system", "sing.db"), "rb").read(), + "Branches": config, + "CommitHistory": [ + FileWrap(i, open(os.path.join(".sing", "control", i), "rb").read()) + for i in os.listdir(os.path.join(".sing", "control")) + ], + } + db = pickle.dumps(db) + c = list(chunks(db, CHUNK_SIZE)) + for i, chunk in enumerate(c): + print(f"Sending Objects... {i}/{len(c)-1} {cycle()}", end="\r") + sock.send(chunk) + print() + print(f"Origin -> HEAD[{remote_name}]") diff --git a/.sing/stage/lib/__pycache__/abc.cpython-311.pyc b/.sing/stage/lib/__pycache__/abc.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5315cc7f0e2664f80e557c1cc730985d1389aed GIT binary patch literal 6003 zcmcIo-ESMm5x>0;@dYSR$U7;4 zTC_{v-0sfY?%d38XJ_xvfq;*LQu_QEwIFlcKd@mHu~FIi3slxQg;Pj|yGnR&hEw>P zoFd%eIqp7OX1U5UjtCsd#Ss|C101i5BQlN*oHiFnVjLfE+FcwE^W)vOYd$( zK08>7*d05lTuh*dOG8dAWc6e^s~K$xn!J%bY@beSdqiAJL|z@D;{ z$c5c%;iy^+$SPOu>Me+CZB?ep-6>F@+;g(ha}qiO)!rY$Em(jb4S_U8dl(+7>Ps~1 za3nerC*Sdb^v?{#-&!DfS6gFj-BG(oc$F{up+F8d0qjCDPo@F;Jw%dk0??O zXkI|7+Yp5wQTLc5B6ThX3gFBVh2S%a7ikWRv%>HD`F;SISVA1_DtV&FIN}|6yZiPV zn&Yp%o#$~m#0_oRGO6%&pX86(? z_*+kAQ>wXu+BBU40MoO}AlbnrGwOS4#vqy@%&E(U^jKiWijaK?bYg~KV7$YCOR5)EcQ1u6DJc;yt)6uk=P1R`YP32kTxTaOPbnju+_y3Kr|xnnRtd#w90`n7!;#{p^~9%%(xtnv z{ozXaz-Z;bXgNGuB>$?0!UdVZF&-lfkq~s`L;_bqB4PLviTMR(DT8%?BJu80GSlD? z-4I_+Yq}v_%BB~xv zY>uC;482qtJXhnO*G+suDN^HrluonT_Y!kz#DybbTfp9#0FE^iUj&Vl;%9fgyw*fOPHJcjq|1)ZwN z`354aM{z8yGeqEWaU%jfMn`~g54O%(*x~Q-rgxr#zQ*p!HY3w7;l?z!ThMS0JXV9&Ny!vN_}?OEu^1uWH= zEFxtG^BvGj?nb@VLK|J1@*(@rj1F(hmJlEo6>9+q8jIqZObQa^p!jf)y8@+o33>zLt*nG|mjxBH7JFR|s9;E3DK&<6#ckH#CEv;3q+50j%NqF;dL4G|Wra*wdJrlEn&l#fnB6`j(Mqn<1$9A{<~M$?aOg?a}(K#Yry(p z#JZc9SjX8MSVx;;*++zHEH3Q-BM308F=x{k zp|%SFeoH3=z-g2~Rbj0F0G2=b;MND_&_E?Luqh9imc`uSdt%sLzGt>Xg3Du{F)WIH zbrW-T9BFUAjsd4NhC!r)D2Dr%io2LAoq#BYd5&ZV=hWpChIy;liw_~>-ou0YS)c6n#?K-^GT}P+4i z)U_kq8I;0-!CM`;^LVSB2jM0+_Qz`P5ok4!WR4(%!5_8VsR3E%bON_Vfq-MT6ynU+ zO4dTMgxOu$R}Kf`iq>Oz7E-`|&7|~q(Eh!!pIneVEACg)dEzp+{FoHa2{*1io;x-? zJRJS`^vv0K^u_7(@#%}v)90_edhwN+^QT@tb0vCi`brcee6%0b%^mfj^mKgchh!l` z5*HTLEd2#MVs7i0A+$KxwG83g$I|>_PDamS4FRC70$Jt0mi>i`YnO{^S$?b{KejC& z*^-Y`{ZDQCC${_(W&dQwKUocStX;1QB+&g$Fj^eH{p@BiYW}x_$G3vV%fVPB7=z~aTc&8&WJG&yD9>j0eaF~Ks+iU4h)+1?KhRzyWqq>MN(tw{5p6%WC*&;ys07o7OW zi_W)L@nQlAsfqyW+j|G_d*8+|ro(ZAr`IT^%@5F0<{6VW+7-H#ol~;FwxPjPCa zf1|&2q1+p*^v3Rvm%GL)U1No|YN(^26`v?-8&joNIW$xW4Q~@{R&tV^u8MriDf~)qg#*5OAYn)B|9tj|ClQ8Fr)OfEnT;qV$ z1!&%UquIfF%}of$2X}b%daAyuwp)B4xKT?D=e>?U>Wd)Q=*2;>Zb&k1|dI_rPf$~`C%I3w&Pb?k#!U~$83 p1oErhq>g6EN>?QpdiBdoY;6{|(*UXu|*i literal 0 HcmV?d00001 diff --git a/.sing/stage/lib/__pycache__/db.cpython-311.pyc b/.sing/stage/lib/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f94b661849dc70375d0ab75ece313a4a084acaba GIT binary patch literal 2594 zcmb_d-%BG`6ux(UWhN#@-Q8BBxNhyTjbdWi3R~DN2uq8*;6CKZDRf9O<2G)7*gF%f zA%YK~Xc1~bRtrAlq1cLj>q}o+@IO%SK?a0EpSDjC_d)il=iEug#A>0i_c~wBJ?Gx* zneTpc?oZ)xkU*>b^1%9AAmlF`G@H~g&K`oXNlaqW9MOp>m|~9R1zpICx|o-Ai4uW) zMog(nOnC=(*@MSxx@^)Ui7BseIQ@zO#za@0dX{vUmCCz;six8JWW04AHn%zZ6vmsx zB04pRE|{7rRwZ4;f2d2Q3@DpHQ>jv2F#|9PSgNU3$quaA^Y_y=Q-D-DT=j9vNu^V^ z#3lO)6BLt;w{!q?&qS)i265DMLkP?9CByUcLEH*;5*E{iNZ z8k1dNw%|&|d8^A{YdyVn_xsx$x9fz4KdL=B8F+8|>CeH#f%``T_t&KjtuBI}2RWSLwATn1zH6=X z^Yd$CJaAr@sMden8(n$4HnqY#tg*)+(bcZWF!>=mNyJ|}z}hF$q!iefM6?4GZI#<$ zZL&|y_y-Fs(Z=U23ILK3Dr62UuT?01K6qribUH>^7mQ*e>xLg!d|@rRvQ=={Vv}%o z0|W8`P;HeveOmdr6GApF1Wx+~fm3aLc(ro{`WZM$#upb1d>L=RV3Rm4G04szT_Iq4 zjezOf6)@|7#9}fF!w;7q!`EXG1kRDbIbuEFu<-*O8uGYNO7kFHs~qvk@f=8u{SCk} zISF^KzrS&BYvM3`=O}y!PF@?Y=_kE|+a0whhrNlT-UOaMC`}vZK@OlaZ5))=>br|Ov6R^-Oo;&)5mE^Q@i_YNZ? zN0E^Ob%dd^uh!Q~h`bejkvU|9x5}lmiY4#=m&h0v!38XWmqZ4&uauu}Rhg?bN?*3n zuC+`tpU*liTJs#pmAwjZrPkuLnUhe@+S4s*TdRG17#ceYjU9x>P;POQkq0QZILfWX zcM-j0HRN{vR8MaGkHkNuNg=Q=Q^2-+%`Z}z`zGAj6}a2lKk0^?c0o|Cn913$JX=WR ztu{sinPQ<}Wt?)CFQA5J06D=gcbWVd=~=PYCsxaAO zaB#1eAf^>lc(rrIR64^Xa4<}iVbSJkUwWT6>O+sBM=TCgusYaZ0Dz(>JtpC0-jB)X W!S#Q2rH?{#|Atfay}lyA)BOkbk`GA$ literal 0 HcmV?d00001 diff --git a/.sing/stage/lib/__pycache__/dirstat.cpython-311.pyc b/.sing/stage/lib/__pycache__/dirstat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..120cf41396909ad35a2a65ce7a15ed5dc9cfa418 GIT binary patch literal 2998 zcma(T-)q}O_)e04%dwm$Oy5hg(j&cYNmOFMomZOzUwoCL6k8jfmxwlNXtt4Uf;Fp@Qx7_#1eyZv zTotO+1+>@+%N zQfTfqb?OxFGpQ9B`)E;2Ak&jf$O>6789`YzT?v^Wp^SiJ=@wo}ab*RqneK!b6% zWJOR;W!4a}gawG%<$D<9!5-I{pQ$qLjp@R*Z(l|C$7l4&m=PH(%#?)9%Z1Bi_x0_YTQ_$v>cM^^*k1#X z2o7jao47Z5fAV=`>{({+Bm`t2H(9$OaQ z4=g~H{sDdlz5_V;nHVA$LByhnDB@Ww@^H8-Qc5=c2|1C*ahZ&D^t6qQ6`+eu#o}IoseY>JIrH6SV%i{U zwcM5df9D-+3dM&?q~#p^Kf`G=ttB^G^PCPdVlZj@4xB$=+%GU-N4T1ZJ&!kO-DIwQoFQJ9Fi zR&igEP!z9u1`Xi}4HGa(zzGawa(FY{MhTsO_XzJ#3~T0^JGYhFy`qN(jL-n?F2TVHcXTIsC%7x6_1{aZ6uG%BE*gkOf6Sh?FJl z8}gs+)_mIZB~kX4h1GksRi%j8o&ZaMRl8n$FQit)Y*P9Noy9ED+vMLcsL>F@3OG?d z?5zM_-g+v4E8FiL?a!Ytd{|o0>3)MIH4F|_LcPVW^V9k1y-=?aI+35QxdG*`Q9js> rJp#M`rZKwFTPO5dEI*9afDIaaLO5>mS;&`qY5-47&=7M_t&jW{gyTLf literal 0 HcmV?d00001 diff --git a/.sing/stage/lib/__pycache__/keyexchange.cpython-311.pyc b/.sing/stage/lib/__pycache__/keyexchange.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e951aa06ff293c6fb5ed0caa6154ef2072a3e66 GIT binary patch literal 1501 zcmaJ>-)q};5dTX0TDGMq&1PpmTAI2J-6kjw8zV_awwILAWzdIZgP}eI)%w;}BFW~f zZ`Rq!ppC*x+2~$M4Wro6KJBE`zuK_#>3nzJyYqeTbMl{x zA|ar`^G)&{kI;+M7!%kToqi8rPmzHP&O$b)aa^kN7HKvMjDg585 zC%kd`?zFzgdC21-24**$%&&($G?=;lG=3h5*ZL@W+D)6dlAz;s(P59cgRcB-lT=_yDyCcMzVpiX(5+D2FTE|zMJM$fL^Yy*$!LVg*zQuIyG5m%`)$&D})M_c{;;Jv>-3+44lUiWXtvhsN2 z(Z-XFXP^JG9$xzR-*1A_W?0&cK*~2F`G$XUv{Zg_XHW{4uSLt(1}t2<9xYw>zZ&6U zAYJYYAb>->65*8quVn6Oh*u-L8sJqhAF3fPN4Ol|@<>u18ozJ%%l%F$sgb1m+cD1n za_i??KkYo&@prQ1tNl*{=_+uDs}ZgSxEjlg{>~{}3aN`psZKj>)@o3_-R#uvW&a&# z5S7p7xCcPiCL_&Y$N)*iGiL@CYzhNp8Z)4!?^Gh_ck3Bi*);z~^b&aK`ygHxw(OR1 zXpt{y70mQ)bbkYJB5)iRqYr|4j?pK str: + s = "" + for key, value in self.contains.items(): + s += ( + "├" + + "─" + + "─" * (4 * level) + + " " + + key + + ("/" if isinstance(value, DirWrap) else "") + + "\n" + ) + if isinstance(value, DirWrap): + s += value.stringify(level + 1) + return s + + +class FileWrap: + def __init__(self, fname: str, fdata: t.Union[bytes, str]) -> None: + self.name = fname + self.data = fdata.encode() if isinstance(fdata, str) else fdata + + +######### +# Streams +# + + +class IStream: + def __init__(self, file) -> None: + self.file = file + + def write(self, *data): + self.file.write(*data) + + +class IOStream: + def __init__(self, fdin: IStream, fdout: "OStream") -> None: + self.fdin = fdin + self.fdout = fdout + + def write(self, *data): + self.fdin.write(*data) + + def read(self): + return self.fdout.read() + + +class OStream: + def __init__(self, file) -> None: + self.file = file + + def read(self): + return self.file.read() + + +class Key: + def __init__(self, kpath, key, hash, randomart): + self.kp = kpath + self.key = key + self.hash = hash + self.randomart = randomart + + def dump(self): + open(self.kp, "wb+").write( + "--- BEGIN FERNET CRYPTOGRAPHY KEY ---\n".encode() + + self.key + + f"\n{self.hash}\n".encode() + + "\n--- END FERNET CRYPTOGRAPHY KEY ---".encode() + ) + + @classmethod + def kimport(cls, kp): + k = open(kp, "rb").read().splitlines() + key = k[1] + hash = k[2].decode() + from random_art.randomart import drunkenwalk, draw + + randomart = draw(drunkenwalk(key), hash) + return cls(kp, key, hash, randomart) diff --git a/.sing/stage/lib/db.py b/.sing/stage/lib/db.py new file mode 100644 index 0000000..ee68c23 --- /dev/null +++ b/.sing/stage/lib/db.py @@ -0,0 +1,30 @@ +import pickle +from .abc import IOStream, IStream, OStream + + +class Database: + def __init__(self, fn) -> None: + self.fn = fn + try: + with open(fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + except: + self.data = {} + + def write(self, key, entry): + self.data[key] = entry + + def update(self): + with open(self.fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + + def get(self, key, default=None): + return self.data.get(key, default) + + def commit(self): + with open(self.fn, "wb+") as stream_in: + pickle.dump(self.data, stream_in) + + @classmethod + def connect(cls, fname): + return cls(fname) diff --git a/.sing/stage/lib/dirstat.py b/.sing/stage/lib/dirstat.py new file mode 100644 index 0000000..bf4f84d --- /dev/null +++ b/.sing/stage/lib/dirstat.py @@ -0,0 +1,51 @@ +from .abc import FileWrap, DirWrap +import os +import functools + +if hasattr(functools, "cache"): + cache_fn = functools.cache +else: + cache_fn = functools.lru_cache + + +@cache_fn +def parse_directory(dirp): + structure = {} + os.chdir(dirp) + for d in os.listdir(): + if os.path.isdir(d): + structure[d] = parse_directory(d) + elif os.path.isfile(d): + structure[d] = open(d, "rb").read() + os.chdir("..") + return structure + + +@cache_fn +def wrap_directory(dirp, level=0): + structure = parse_directory(dirp) + data = [] + for k, v in structure.items(): + if isinstance(v, dict): + data.append(wrap_directory(k, level + 1)) + else: + data.append(FileWrap(k, v)) + if level == 0: + os.chdir(os.path.join("..", "..")) + return DirWrap(dirp, *data) + + +@cache_fn +def unpack(dirw: DirWrap): + import shutil + + for k, v in dirw.contains.items(): + if isinstance(v, DirWrap): + if os.path.isdir(k): + shutil.rmtree(k) + os.mkdir(v.name) + os.chdir(v.name) + unpack(v) + os.chdir("..") + else: + open(k, "wb+").write(v.data) diff --git a/.sing/stage/lib/keyexchange.py b/.sing/stage/lib/keyexchange.py new file mode 100644 index 0000000..72097d3 --- /dev/null +++ b/.sing/stage/lib/keyexchange.py @@ -0,0 +1,20 @@ +from cryptography.fernet import Fernet +from random_art.randomart import drunkenwalk, draw +from random import choices +from string import ascii_letters, digits +import os +from .abc import Key + + +def generate_keys(): + key = Fernet.generate_key() + path = os.path.join(".sing", "system", ".keyfile") + hash = "".join(choices(ascii_letters + digits, k=10)) + randomart = draw(drunkenwalk(key), hash) + print(f"The Key is {key}") + print("The Key's randomart is") + print(randomart) + key = Key(path, key, hash, randomart) + key.dump() + print(f"Saved Keys in {path}") + return key diff --git a/.sing/stage/sing.py b/.sing/stage/sing.py new file mode 100644 index 0000000..512db0a --- /dev/null +++ b/.sing/stage/sing.py @@ -0,0 +1,419 @@ +import typer +import os +import typing as t +import shutil +from pathlib import Path +import cson +import getpass +from lib import abc +import string +from lib.db import Database +from lib.dirstat import wrap_directory, unpack + +import socket + +from hooks.remote import remote, clone as _clone +from hooks.diff import diff + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + +app = typer.Typer(name="sing", help="Singularity - the worst Source Control ever") +app.add_typer(remote, name="remote") +app.add_typer(diff, name="diff") + + +@app.command() +def clone(url: str = typer.Argument(...)): + _clone(url, init_fn=init, pull_fn=pull) + + +@app.command() +def init( + dir=typer.Argument("."), + branch: str = typer.Option("master", "-b", "--initial-branch"), + force: bool = typer.Option(False, "-f", "--force", "-r", "--reinit"), +): + """ + Initializes a new Repository, or reinitializes an existing one + """ + initial_cfg = {"current_branch": branch, "branches": [branch], "remotes": {}} + user_cfg = { + "user.name": f"", + "user.email": f"", + "repo.name": os.path.basename(os.path.abspath(dir)), + } + if os.path.exists(os.path.join(dir, ".sing")): + if force: + shutil.rmtree(os.path.join(dir, ".sing")) + print("Reinitializing Singularity Repository...") + else: + return print("Singularity Config Directory already exists - Quitting") + os.mkdir(os.path.join(dir, ".sing")) + os.chdir(os.path.join(dir, ".sing")) + os.mkdir("branches") # Branch Directories + os.mkdir(os.path.join("branches", branch)) # Initial Branch + os.mkdir("stage") # Staged Changes + os.mkdir("system") # Remotes, Branch Config, Local Config, Database + n = open(os.path.join("system", "sing.db"), "wb+") + n.close() + cson.dump(initial_cfg, open(os.path.join("system", "branchcf.cson"), "w+")) + cson.dump(user_cfg, open(os.path.join("system", "localconfig.cson"), "w+")) + os.mkdir("control") # Timeline etc. + os.mkdir("overhead") # Stashed Data + print("Initialized barebones Repository in {0}".format(os.path.abspath(dir))) + + +@app.command() +def config( + key=typer.Argument(None), + list: bool = typer.Option(False, "-l", "--list"), + set: str = typer.Option(None, "--set"), +): + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if key: + if set: + ucfg[key] = set + cson.dump( + ucfg, open(os.path.join(".sing", "system", "localconfig.cson"), "w+") + ) + else: + print(ucfg.get(key, f"Not found: {key}")) + if list: + subpart = {} + for k, v in ucfg.items(): + root, val = k.split(".") + if root not in subpart: + subpart[root] = [] + subpart[root].append((val, v)) + for root, values in subpart.items(): + print(f"- {root}") + for key, value in values: + print(f"---- {key} -> {value}") + + +@app.command() +def log(): + """""" + for commitfile in os.listdir(os.path.join(".sing", "control")): + print(open(os.path.join(".sing", "control", commitfile)).read()) + + +@app.command() +def stash(files: t.List[Path]): + """ + Stashes Files into Overhead to avoid conflicts. Usually called automatically + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "overhead", bn)): + shutil.rmtree(os.path.join(".sing", "overhead", bn)) + shutil.copytree(fp, os.path.join(".sing", "overhead", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "overhead", bn)): + os.remove(os.path.join(".sing", "overhead", bn)) + shutil.copyfile(fp, os.path.join(".sing", "overhead", bn)) + + +@app.command() +def add(files: t.List[Path]): + """ + Stage Files or Directories for a commit + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "stage", bn)): + shutil.rmtree(os.path.join(".sing", "stage", bn)) + shutil.copytree(fp, os.path.join(".sing", "stage", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "stage", bn)): + os.remove(os.path.join(".sing", "stage", bn)) + shutil.copyfile(fp, os.path.join(".sing", "stage", bn)) + + +@app.command() +def rm(files: t.List[str]): + """ + Unstage staged Files + """ + for file in files: + if os.path.exists(os.path.join(".sing", "stage", file)): + if os.path.isdir(os.path.join(".sing", "stage", file)): + shutil.rmtree(os.path.join(".sing", "stage", file)) + else: + os.remove(os.path.join(".sing", "stage", file)) + + +@app.command() +def branch(make_new: str = typer.Option(None, "-m", "--new")): + """ + List Branches or make a new one. To switch, use the 'checkout' command. + """ + if not make_new: + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + for branch in cfg["branches"]: + if branch == cfg["current_branch"]: + print(f"* {branch}") + else: + print(branch) + else: + os.mkdir(os.path.join(".sing", "branches", make_new)) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + cfg["branches"].append(make_new) + cfg["current_branch"] = make_new + cson.dump(cfg, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@app.command() +def commit( + message: str = typer.Option(..., "-m", "--message"), + amend: bool = typer.Option(False, "-a", "--amend"), +): + """ + Commit the staged Files and write them to the Database + + Options: + -m, --message : The Commit Message. + -a. --amend : Overwrite the last commit. + """ + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if ucfg["user.name"] == "" or ucfg["user.email"] == "": + print("*** Please tell me who you are") + print("\nRun\n") + print('\tsing config user.name --set "Your Name" ') + print('\tsing config user.example --set "you@example.com" ') + return + if amend: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))-1}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + else: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + +@app.command() +def stat(): + """ + Print the entire sing Configuration Tree + """ + dw = wrap_directory(".sing") + print(dw.stringify(), end="") + print("local{HEAD}") + + +@app.command() +def slog(): + """ + List all Commits in the Database + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + for k, v in db.data.items(): + print(f"{k} --> {type(v)}") + + +@app.command() +def pull(): + """ + Stash the current Tree and integrate changes downloaded from Remote into active branch + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + stash([Path(".")]) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, id, branch = i.split() + if branch == cfg["current_branch"]: + break + revert(id, branch=branch) + + +@app.command(no_args_is_help=True) +def comtree(c_id: str = typer.Argument(...)): + """ + View the Tree Structure of the Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + print(entry.stringify()) + print("HEAD/") + + +@app.command(no_args_is_help=True) +def revert( + c_id: str = typer.Argument(...), branch: str = typer.Option(None, "-b", "--branch") +): + """ + Reverts to Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +@app.command(no_args_is_help=True) +def checkout( + branch: str = typer.Argument(...), + force: bool = typer.Option(False, "-f", "--force"), +): + """ + Switch branches or restore working tree files + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, c_id, _branch = i.split() + if branch == _branch: + break + + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + if not force: + stash([Path(".")]) + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +if __name__ == "__main__": + app() diff --git a/.sing/stage/test.py b/.sing/stage/test.py new file mode 100644 index 0000000..929db5c --- /dev/null +++ b/.sing/stage/test.py @@ -0,0 +1 @@ +# Lol diff --git a/.sing/system/.keyfile b/.sing/system/.keyfile new file mode 100644 index 0000000..f75c627 --- /dev/null +++ b/.sing/system/.keyfile @@ -0,0 +1,5 @@ +--- BEGIN FERNET CRYPTOGRAPHY KEY --- +oHVd1Vltn_kSHazxhcr0Mi3v35EnjzEcncGPDdantVc= +s7IyTiiBD4 + +--- END FERNET CRYPTOGRAPHY KEY --- \ No newline at end of file diff --git a/.sing/system/branchcf.cson b/.sing/system/branchcf.cson new file mode 100644 index 0000000..e2a0ef3 --- /dev/null +++ b/.sing/system/branchcf.cson @@ -0,0 +1 @@ +{"current_branch":"foo","branches":["master","secondary","foo"],"remotes":{"text":"192.168.68.110","upstream":"127.0.0.1"}} \ No newline at end of file diff --git a/.sing/system/localconfig.cson b/.sing/system/localconfig.cson new file mode 100644 index 0000000..33cd468 --- /dev/null +++ b/.sing/system/localconfig.cson @@ -0,0 +1 @@ +{"user.name":"Jane Johanna Yosef","user.email":"stupidjane@tutanota.com","repo.name":"singularity"} \ No newline at end of file diff --git a/.sing/system/sing.db b/.sing/system/sing.db new file mode 100644 index 0000000000000000000000000000000000000000..3cf1cfa57e7f5aed9b26b3eb5d3c692130d2a3d3 GIT binary patch literal 336354 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 CUuO*f literal 0 HcmV?d00001 diff --git a/hooks/__pycache__/diff.cpython-311.pyc b/hooks/__pycache__/diff.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0edd7bc77759107a76fce8a63815b06d2e968e6e GIT binary patch literal 2661 zcmc&$OKcNI7@mE3Z6|98N$ia=&PJpuPB7sm(N+YB7LrQ{L_wt(A!NMkcpZD!+TB27 z9Ar==P!*{L1;t9CSXB!~aLA#D969#TL$+F})=HL;svLUTlvZsyb$0zo9O6nymHLlo z=Ksh4>-*6ZW*7gL08n9O=im)W- zh@z~0j%c(EIay=^1RTGbieoy?^3iAj(QzT1UvdTp2DMBL$ z>}H_XU*i^dWD=Q06u3~McwM`I*48%9EZPWLI+{TW_0%_ODrT%IZM7=Y3t7`J=SHxN zaS)(T8@lm&p*Kfqf}>vS4SN7LoyAzZvG9!%8>Lw^gR(?d-Nh`H0Xal^rqt^NK)jCl z9W$|Iuz${zRn;=-)-sk_6xdnPl=8h(r z|JkrZ&h52w$b8H8XRq-VodTCHzT3(9uPY?Gt7mCf1Fn|sam|0mwoU-E2dMN}Cj;pd zF_{WW9CI?F@Yguzf+$V#!X$Hwm$--`N;6DYV3|Z%;beu0h{+_cFrvUHF^-YpyU1LX z!a^iwc}f!pWN4bU!)a0wB~G`=GqS=Z(^N$(5{v*}h zr@8xnb|3u7eNc5D*4&3x`w`84WYvD8OtsBle4sq~_~FRo(WTL%XL;YsS=BeH`9@XG z8O?J>rN%UBEIV9sb*y&2r@8vFBPG(c4mt^@+}6I}-DKInGO7B8HQ%t>HlnqS7{KM| zUpI%lC(G@f3&$2u+&@w1QQLd9_Fe-}RDW@NxvzMsc&WUzGZ)Jr%EfZAdpj0(D7@s;;q9#ZMk8hv^lLuvQ#01j)kU!w!X9+lp& z(ffZRP&#P9_23^i(B;p^3*-5CKCaRMsI+Q*%3a-BSATQAUemAlZNFaAueZ71HG0om z;r_NNbr1A0-ZE3B)p%x?QBZn;myLYiekb#ZO?$@H?4hecw;}F6xrzncS7oeqWPOD zFT%mV5CkcK)~wY^pe1Xy5^!X#Rsznf)k=WMTJ06ND?2@RBX3(JyH&DVBfGP>VFRe` zj_hi!`8z2;?d>ZDm|ppLk9385GNdR9UHvGtz!V)Ip;UfnufJ%CH@b7 CEGRet literal 0 HcmV?d00001 diff --git a/hooks/__pycache__/remote.cpython-311.pyc b/hooks/__pycache__/remote.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d80634b3c7c9f8b178bc243831f1155c062b963d GIT binary patch literal 17296 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
{nHiL7&&suDCHIi*9!i;0<}cR_q^UgZF2e_S z{PN4RqXyXgu{@V)M=e-U(IN;SMXoyBx3}MFzSHtn%hK$M_1^wpJ|lS!%bvscyCes& zK?f4K?jwK3_PN-8>*V!;g@M!n(ij@ag3mDa``lwbqbP>6vPr2g!KiDp{@Alnd9&_D z-QxJt%PZBA^(oo<)V=c(^_)yS_mFCTK(&7aY7^~b45WX; z6BVo}uM=#p({8Z!72+QG6RgJ)Rcp6@hd!$l_Q{-m zL%#>JHTwQQYj-3Z%Xv~{2Mu>jV?uj|&g^_5z?dYnRQd<%dCy? zuO7n|*l)1p7QQP~t-bDRwD$UqqXrGVfj-RsEPBhFCv;9-q+`%U?i%UOWB;01B-svYf!<-pId* z;4*@j5c~i@+=r(=w1a!7e>J0uuWD%woEV5`nk<9II0Rp9v0|rpH)^^cx*Pcb0u}*> zU<|=H0`w=T$|paGu_**~2*L;=2%-pnh=50sQyzI#$9O!C>T~iZFpj&IVhL%4;flF5My0yWsk+s$UBT%u{m39?{tX*F)!1 z51da)&I7XZfJ8kbQ_qOhGphFYe@IFD7FrpoI^XbQX!Wk;xLo}t91lG#Q5FA>^{W>} zTdQ_SRGUn-iBy}aU*-7=H7&KBaOZ@tLZRi(<-)f*2)u{9t)*TxU4 zKCAP(??ZTQOn}2tcw%-o7@J_=m}w*sgbzq^{x8kZRphw21|%(#v*X5@D8v5V$o-l{TvRQHmb<182C}$>jV$9py2&E%82-tRf2fw?7YrE1}Y(lj@huq zw+U1>zwnM9rxt(gR_y-VT-DbTT|bp?8Wx6txJFGYQB{IJhhBYV9+BAe93eP}{45-D zHncsVo#sU+V&)w0y6uR1h7Y`M$Kq8{IAz{;MAdJEV-Ug9eVFr~P(c_00In3mSYykB zN2TsBjD*9xcvdGXKYQOV)ju!SKmXAVsj6G9>V8<&_n@jzs_K`k`X&2-YzLdLt|3hU z!tub^^mA*e;DQH$1onyae$8Kl|!{iG2sVG0)GIa32?L^P{esBTyj&vzjE<6JV zJYhMgdX>+XSC#lTutq0>oSLK<*inTHsI!W$Kl}s|wul0aNb`zOII;W^Dl>g-MC3mZ@ry zs#Yb|y!+=9PsMHi*6f|cTZ!fFl^5>KNtNw#W&1}@OP(It)AP`C=7HyoMyXa2@?#T}&2>&AdhcNOpWts`{Vu{=!wO zYoTs&RI*mf*6L)(DsBJqg~g7$HqG}Y(K}=$!fJW_lK;)VTYc!L0f!iX>vW1t9Z;QY z$Cleg=P|f`VO(i{-~6F@<%jRnTFO71|GSI7yr{)L>J;f-iSCu@UWlOk&3ePSxKpC5 zWV%XJFQCqA-PgL4-71;(ES&}gSG#f!PVjNUrLJb*PQ`?uKk%ACVF$7^ z1~Yom*-`w2OmK=D&NHR^> zlj<)`IFjlwO_U_nUz)Hc)nA&hCDmU+nOc$1U1?&QxY?B^e4_qZGn>iEH0@297aZXB z4KI($yADe9A(=je%g2OnO3 z#?Z3}4VF|SJMo83Ix}JOro0QaOS?sDlVoj@txZYuBifoYJ#sjcwl%Yngzuqqz-}hN zDVqZx!a%>#@Opzpx@FS6X0VXMWGVnF0`5yj?Oq5!GZF?*uH;&IruyQABs5VclXZ{0 z+ZNB?y1cw&g%aD2N#5hK7e1O`7Q1fsEqhm}l_{zGuv~t4&9svQuOxu9)3auT`y+=t zpQz?wU-i)BngPoWlG(m4VPB0h*$7T)z!?VP#-dCvB|E4*R#+0wL<7h-iYcCFNcdJI y2l#9sS#tQugKGqU{ID_Qh2mn3WXl=>AYUw-q9zL{>- shutil.disk_usage(i): + print(f"+++ {i}") + elif shutil.disk_usage( + os.path.join(".sing", "branches", masterb, i) + ) < shutil.disk_usage(i): + print(f"--- {i}") + else: + print(f"=== {i}") + for i in os.listdir(): + if ( + not os.path.exists(os.path.join(".sing", "branches", masterb, i)) + and not i in ignore + ): + print(f"+ {i}") diff --git a/hooks/remote.py b/hooks/remote.py new file mode 100644 index 0000000..80f2a02 --- /dev/null +++ b/hooks/remote.py @@ -0,0 +1,249 @@ +import typer +import socket +import cson +import os +import sys +import pickle +from lib.db import Database +from lib.abc import FileWrap, Key +from lib.keyexchange import generate_keys + + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + + +remote = typer.Typer(name="remote") + +cstep = 0 + +CHUNK_SIZE = 1 + + +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i : i + n] + + +def cycle(): + global cstep + steps = ["/", "-", "\\", "|"] + cstep += 1 + if cstep == 4: + cstep = 0 + return steps[cstep] + + +@remote.command() +def add(name: str, url: str): + """ + Add a remote named for the repository at . The command 'sing remote fetch' can then + be used to create and update remote branches (/) + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["remotes"][name] = url + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@remote.command(name="keys") +def kg( + generate: bool = typer.Option(True, "-g", "--generate-new"), + publish: bool = typer.Option(False, "-p", "--publish"), +): + if generate: + generate_keys() + print("REMINDER : THIS KEY WILL BE USED TO ENCRYPT REMOTE PUSHES.") + print("Other Clients will not be able to decrypt the Push unless they") + print("receive the key. Use these commands to share your keys:") + print("\tsing remote keys --publish") + + if publish: + if not os.path.exists(os.path.join(".sing", "system", ".keyfile")): + return print( + "Fatal -- no Keys found. Use the '-g' option to create new keys" + ) + print("Importing Keys...") + key: Key = Key.kimport(os.path.join(".sing", "system", ".keyfile")) + print(key.randomart) + import getpass + + print("Do you want to protect your Keys using a Passphrase?") + print("Recipients will be prompted for their Password before getting the Key") + p = input("[y/N]") or "n" + p = p.lower() + if p in ["y", "yes"]: + passphrase = getpass.getpass("Enter Passphrase : ") + pass_rep = getpass.getpass("Re-Type Passphrase:") + i = 1 + while not pass_rep == passphrase and i < 3: + pass_rep = getpass.getpass( + "Wrong Passphrase - Please re-type Passphrase:" + ) + i += 1 + if i >= 3: + return print("Aborted - 3 Times entered Wrong Password") + else: + passphrase = "" + + +@remote.command(name="get-url") +def gurl(remote_name): + """ + Retrieves the URLs for a Remote. + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + rmurl = config["remotes"].get(remote_name, "Fatal -- No Remote Found.") + print(rmurl) + + +@remote.command(name="list") +def lst(): + """ + List all Remotes URLs + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + for name, url in config["remotes"].items(): + print(f"{name} --> {url}") + + +def clone(url, init_fn, pull_fn): + """ + Download objects from remote repository + """ + import urllib.parse + + parsed = urllib.parse.urlparse(url) + if os.path.exists(parsed.path.split("/")[-1]): + return print(f"Fatal -- File/Directory exists: {parsed.path.split('/')[-1]}") + print("Connecting to remote Server...") + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(parsed.hostname), 2991)) + s = f"down {parsed.path}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {parsed.path}".encode()) + + database = [] + print("Initializing Repository...") + os.mkdir(parsed.path.split("/")[-1]) + os.chdir(parsed.path.split("/")[-1]) + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + print("remote: Decompressing objects...") + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + init_fn(".", Branch_Config["current_branch"], True) + os.chdir("..") + try: + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + except: + config = {} + config = Branch_Config + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{url}/HEAD -> local/HEAD") + print("Pulling changes...") + pull_fn() + + +@remote.command() +def fetch(remote_name): + """ + Download objects from remote repository + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"down {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(1024) + sock.send(f"down {lconfig['repo.name']}".encode()) + + database = [] + while True: + print(f"remote: Receiving Objects... {cycle()}", end="\r") + sock.settimeout(1) + try: + packet = sock.recv(4096) + if not packet: + break + database.append(packet) + except Exception as e: + break + print("remote: Unpacking Objects...") + database = b"".join(database) + database = pickle.loads(database) + Main_DB = database.get("MainDB") + Branch_Config = database.get("Branches") + Commit_History = database.get("CommitHistory") + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + config["branches"] = Branch_Config["branches"] + cson.dump(config, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + for i in Commit_History: + with open(os.path.join(".sing", "control", i.name), "wb+") as f: + f.write(i.data) + + open(os.path.join(".sing", "system", "sing.db"), "wb+").write(Main_DB) + print(f"{remote_name}/HEAD -> local/HEAD") + print("Use 'sing pull' to write into local files") + + +@remote.command() +def push(remote_name): + """ + Update remote refs along with associated objects + """ + config = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + lconfig = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + rmurl = config["remotes"].get(remote_name) + + print("Connecting to remote Server...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((socket.gethostbyname(rmurl), 2991)) + s = f"up {lconfig['repo.name']}" + sock.send(f"{len(s)}".encode()) + sock.recv(8) + sock.send(s.encode()) + print("remote: Building Database...") + db = { + "MainDB": open(os.path.join(".sing", "system", "sing.db"), "rb").read(), + "Branches": config, + "CommitHistory": [ + FileWrap(i, open(os.path.join(".sing", "control", i), "rb").read()) + for i in os.listdir(os.path.join(".sing", "control")) + ], + } + print("remote: Compressing Objects...") + db = pickle.dumps(db) + c = list(chunks(db, CHUNK_SIZE)) + for i, chunk in enumerate(c): + print(f"Sending Objects... {i}/{len(c)-1} {cycle()}", end="\r") + sock.send(chunk) + print() + print(f"Origin -> HEAD[{remote_name}]") diff --git a/lib/__pycache__/abc.cpython-311.pyc b/lib/__pycache__/abc.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5315cc7f0e2664f80e557c1cc730985d1389aed GIT binary patch literal 6003 zcmcIo-ESMm5x>0;@dYSR$U7;4 zTC_{v-0sfY?%d38XJ_xvfq;*LQu_QEwIFlcKd@mHu~FIi3slxQg;Pj|yGnR&hEw>P zoFd%eIqp7OX1U5UjtCsd#Ss|C101i5BQlN*oHiFnVjLfE+FcwE^W)vOYd$( zK08>7*d05lTuh*dOG8dAWc6e^s~K$xn!J%bY@beSdqiAJL|z@D;{ z$c5c%;iy^+$SPOu>Me+CZB?ep-6>F@+;g(ha}qiO)!rY$Em(jb4S_U8dl(+7>Ps~1 za3nerC*Sdb^v?{#-&!DfS6gFj-BG(oc$F{up+F8d0qjCDPo@F;Jw%dk0??O zXkI|7+Yp5wQTLc5B6ThX3gFBVh2S%a7ikWRv%>HD`F;SISVA1_DtV&FIN}|6yZiPV zn&Yp%o#$~m#0_oRGO6%&pX86(? z_*+kAQ>wXu+BBU40MoO}AlbnrGwOS4#vqy@%&E(U^jKiWijaK?bYg~KV7$YCOR5)EcQ1u6DJc;yt)6uk=P1R`YP32kTxTaOPbnju+_y3Kr|xnnRtd#w90`n7!;#{p^~9%%(xtnv z{ozXaz-Z;bXgNGuB>$?0!UdVZF&-lfkq~s`L;_bqB4PLviTMR(DT8%?BJu80GSlD? z-4I_+Yq}v_%BB~xv zY>uC;482qtJXhnO*G+suDN^HrluonT_Y!kz#DybbTfp9#0FE^iUj&Vl;%9fgyw*fOPHJcjq|1)ZwN z`354aM{z8yGeqEWaU%jfMn`~g54O%(*x~Q-rgxr#zQ*p!HY3w7;l?z!ThMS0JXV9&Ny!vN_}?OEu^1uWH= zEFxtG^BvGj?nb@VLK|J1@*(@rj1F(hmJlEo6>9+q8jIqZObQa^p!jf)y8@+o33>zLt*nG|mjxBH7JFR|s9;E3DK&<6#ckH#CEv;3q+50j%NqF;dL4G|Wra*wdJrlEn&l#fnB6`j(Mqn<1$9A{<~M$?aOg?a}(K#Yry(p z#JZc9SjX8MSVx;;*++zHEH3Q-BM308F=x{k zp|%SFeoH3=z-g2~Rbj0F0G2=b;MND_&_E?Luqh9imc`uSdt%sLzGt>Xg3Du{F)WIH zbrW-T9BFUAjsd4NhC!r)D2Dr%io2LAoq#BYd5&ZV=hWpChIy;liw_~>-ou0YS)c6n#?K-^GT}P+4i z)U_kq8I;0-!CM`;^LVSB2jM0+_Qz`P5ok4!WR4(%!5_8VsR3E%bON_Vfq-MT6ynU+ zO4dTMgxOu$R}Kf`iq>Oz7E-`|&7|~q(Eh!!pIneVEACg)dEzp+{FoHa2{*1io;x-? zJRJS`^vv0K^u_7(@#%}v)90_edhwN+^QT@tb0vCi`brcee6%0b%^mfj^mKgchh!l` z5*HTLEd2#MVs7i0A+$KxwG83g$I|>_PDamS4FRC70$Jt0mi>i`YnO{^S$?b{KejC& z*^-Y`{ZDQCC${_(W&dQwKUocStX;1QB+&g$Fj^eH{p@BiYW}x_$G3vV%fVPB7=z~aTc&8&WJG&yD9>j0eaF~Ks+iU4h)+1?KhRzyWqq>MN(tw{5p6%WC*&;ys07o7OW zi_W)L@nQlAsfqyW+j|G_d*8+|ro(ZAr`IT^%@5F0<{6VW+7-H#ol~;FwxPjPCa zf1|&2q1+p*^v3Rvm%GL)U1No|YN(^26`v?-8&joNIW$xW4Q~@{R&tV^u8MriDf~)qg#*5OAYn)B|9tj|ClQ8Fr)OfEnT;qV$ z1!&%UquIfF%}of$2X}b%daAyuwp)B4xKT?D=e>?U>Wd)Q=*2;>Zb&k1|dI_rPf$~`C%I3w&Pb?k#!U~$83 p1oErhq>g6EN>?QpdiBdoY;6{|(*UXu|*i literal 0 HcmV?d00001 diff --git a/lib/__pycache__/db.cpython-311.pyc b/lib/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f94b661849dc70375d0ab75ece313a4a084acaba GIT binary patch literal 2594 zcmb_d-%BG`6ux(UWhN#@-Q8BBxNhyTjbdWi3R~DN2uq8*;6CKZDRf9O<2G)7*gF%f zA%YK~Xc1~bRtrAlq1cLj>q}o+@IO%SK?a0EpSDjC_d)il=iEug#A>0i_c~wBJ?Gx* zneTpc?oZ)xkU*>b^1%9AAmlF`G@H~g&K`oXNlaqW9MOp>m|~9R1zpICx|o-Ai4uW) zMog(nOnC=(*@MSxx@^)Ui7BseIQ@zO#za@0dX{vUmCCz;six8JWW04AHn%zZ6vmsx zB04pRE|{7rRwZ4;f2d2Q3@DpHQ>jv2F#|9PSgNU3$quaA^Y_y=Q-D-DT=j9vNu^V^ z#3lO)6BLt;w{!q?&qS)i265DMLkP?9CByUcLEH*;5*E{iNZ z8k1dNw%|&|d8^A{YdyVn_xsx$x9fz4KdL=B8F+8|>CeH#f%``T_t&KjtuBI}2RWSLwATn1zH6=X z^Yd$CJaAr@sMden8(n$4HnqY#tg*)+(bcZWF!>=mNyJ|}z}hF$q!iefM6?4GZI#<$ zZL&|y_y-Fs(Z=U23ILK3Dr62UuT?01K6qribUH>^7mQ*e>xLg!d|@rRvQ=={Vv}%o z0|W8`P;HeveOmdr6GApF1Wx+~fm3aLc(ro{`WZM$#upb1d>L=RV3Rm4G04szT_Iq4 zjezOf6)@|7#9}fF!w;7q!`EXG1kRDbIbuEFu<-*O8uGYNO7kFHs~qvk@f=8u{SCk} zISF^KzrS&BYvM3`=O}y!PF@?Y=_kE|+a0whhrNlT-UOaMC`}vZK@OlaZ5))=>br|Ov6R^-Oo;&)5mE^Q@i_YNZ? zN0E^Ob%dd^uh!Q~h`bejkvU|9x5}lmiY4#=m&h0v!38XWmqZ4&uauu}Rhg?bN?*3n zuC+`tpU*liTJs#pmAwjZrPkuLnUhe@+S4s*TdRG17#ceYjU9x>P;POQkq0QZILfWX zcM-j0HRN{vR8MaGkHkNuNg=Q=Q^2-+%`Z}z`zGAj6}a2lKk0^?c0o|Cn913$JX=WR ztu{sinPQ<}Wt?)CFQA5J06D=gcbWVd=~=PYCsxaAO zaB#1eAf^>lc(rrIR64^Xa4<}iVbSJkUwWT6>O+sBM=TCgusYaZ0Dz(>JtpC0-jB)X W!S#Q2rH?{#|Atfay}lyA)BOkbk`GA$ literal 0 HcmV?d00001 diff --git a/lib/__pycache__/dirstat.cpython-311.pyc b/lib/__pycache__/dirstat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..120cf41396909ad35a2a65ce7a15ed5dc9cfa418 GIT binary patch literal 2998 zcma(T-)q}O_)e04%dwm$Oy5hg(j&cYNmOFMomZOzUwoCL6k8jfmxwlNXtt4Uf;Fp@Qx7_#1eyZv zTotO+1+>@+%N zQfTfqb?OxFGpQ9B`)E;2Ak&jf$O>6789`YzT?v^Wp^SiJ=@wo}ab*RqneK!b6% zWJOR;W!4a}gawG%<$D<9!5-I{pQ$qLjp@R*Z(l|C$7l4&m=PH(%#?)9%Z1Bi_x0_YTQ_$v>cM^^*k1#X z2o7jao47Z5fAV=`>{({+Bm`t2H(9$OaQ z4=g~H{sDdlz5_V;nHVA$LByhnDB@Ww@^H8-Qc5=c2|1C*ahZ&D^t6qQ6`+eu#o}IoseY>JIrH6SV%i{U zwcM5df9D-+3dM&?q~#p^Kf`G=ttB^G^PCPdVlZj@4xB$=+%GU-N4T1ZJ&!kO-DIwQoFQJ9Fi zR&igEP!z9u1`Xi}4HGa(zzGawa(FY{MhTsO_XzJ#3~T0^JGYhFy`qN(jL-n?F2TVHcXTIsC%7x6_1{aZ6uG%BE*gkOf6Sh?FJl z8}gs+)_mIZB~kX4h1GksRi%j8o&ZaMRl8n$FQit)Y*P9Noy9ED+vMLcsL>F@3OG?d z?5zM_-g+v4E8FiL?a!Ytd{|o0>3)MIH4F|_LcPVW^V9k1y-=?aI+35QxdG*`Q9js> rJp#M`rZKwFTPO5dEI*9afDIaaLO5>mS;&`qY5-47&=7M_t&jW{gyTLf literal 0 HcmV?d00001 diff --git a/lib/__pycache__/keyexchange.cpython-311.pyc b/lib/__pycache__/keyexchange.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e951aa06ff293c6fb5ed0caa6154ef2072a3e66 GIT binary patch literal 1501 zcmaJ>-)q};5dTX0TDGMq&1PpmTAI2J-6kjw8zV_awwILAWzdIZgP}eI)%w;}BFW~f zZ`Rq!ppC*x+2~$M4Wro6KJBE`zuK_#>3nzJyYqeTbMl{x zA|ar`^G)&{kI;+M7!%kToqi8rPmzHP&O$b)aa^kN7HKvMjDg585 zC%kd`?zFzgdC21-24**$%&&($G?=;lG=3h5*ZL@W+D)6dlAz;s(P59cgRcB-lT=_yDyCcMzVpiX(5+D2FTE|zMJM$fL^Yy*$!LVg*zQuIyG5m%`)$&D})M_c{;;Jv>-3+44lUiWXtvhsN2 z(Z-XFXP^JG9$xzR-*1A_W?0&cK*~2F`G$XUv{Zg_XHW{4uSLt(1}t2<9xYw>zZ&6U zAYJYYAb>->65*8quVn6Oh*u-L8sJqhAF3fPN4Ol|@<>u18ozJ%%l%F$sgb1m+cD1n za_i??KkYo&@prQ1tNl*{=_+uDs}ZgSxEjlg{>~{}3aN`psZKj>)@o3_-R#uvW&a&# z5S7p7xCcPiCL_&Y$N)*iGiL@CYzhNp8Z)4!?^Gh_ck3Bi*);z~^b&aK`ygHxw(OR1 zXpt{y70mQ)bbkYJB5)iRqYr|4j?pK str: + s = "" + for key, value in self.contains.items(): + s += ( + "├" + + "─" + + "─" * (4 * level) + + " " + + key + + ("/" if isinstance(value, DirWrap) else "") + + "\n" + ) + if isinstance(value, DirWrap): + s += value.stringify(level + 1) + return s + + +class FileWrap: + def __init__(self, fname: str, fdata: t.Union[bytes, str]) -> None: + self.name = fname + self.data = fdata.encode() if isinstance(fdata, str) else fdata + + +######### +# Streams +# + + +class IStream: + def __init__(self, file) -> None: + self.file = file + + def write(self, *data): + self.file.write(*data) + + +class IOStream: + def __init__(self, fdin: IStream, fdout: "OStream") -> None: + self.fdin = fdin + self.fdout = fdout + + def write(self, *data): + self.fdin.write(*data) + + def read(self): + return self.fdout.read() + + +class OStream: + def __init__(self, file) -> None: + self.file = file + + def read(self): + return self.file.read() + + +class Key: + def __init__(self, kpath, key, hash, randomart): + self.kp = kpath + self.key = key + self.hash = hash + self.randomart = randomart + + def dump(self): + open(self.kp, "wb+").write( + "--- BEGIN FERNET CRYPTOGRAPHY KEY ---\n".encode() + + self.key + + f"\n{self.hash}\n".encode() + + "\n--- END FERNET CRYPTOGRAPHY KEY ---".encode() + ) + + @classmethod + def kimport(cls, kp): + k = open(kp, "rb").read().splitlines() + key = k[1] + hash = k[2].decode() + from random_art.randomart import drunkenwalk, draw + + randomart = draw(drunkenwalk(key), hash) + return cls(kp, key, hash, randomart) diff --git a/lib/db.py b/lib/db.py new file mode 100644 index 0000000..ee68c23 --- /dev/null +++ b/lib/db.py @@ -0,0 +1,30 @@ +import pickle +from .abc import IOStream, IStream, OStream + + +class Database: + def __init__(self, fn) -> None: + self.fn = fn + try: + with open(fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + except: + self.data = {} + + def write(self, key, entry): + self.data[key] = entry + + def update(self): + with open(self.fn, "rb+") as stream_out: + self.data = pickle.load(stream_out) + + def get(self, key, default=None): + return self.data.get(key, default) + + def commit(self): + with open(self.fn, "wb+") as stream_in: + pickle.dump(self.data, stream_in) + + @classmethod + def connect(cls, fname): + return cls(fname) diff --git a/lib/dirstat.py b/lib/dirstat.py new file mode 100644 index 0000000..bf4f84d --- /dev/null +++ b/lib/dirstat.py @@ -0,0 +1,51 @@ +from .abc import FileWrap, DirWrap +import os +import functools + +if hasattr(functools, "cache"): + cache_fn = functools.cache +else: + cache_fn = functools.lru_cache + + +@cache_fn +def parse_directory(dirp): + structure = {} + os.chdir(dirp) + for d in os.listdir(): + if os.path.isdir(d): + structure[d] = parse_directory(d) + elif os.path.isfile(d): + structure[d] = open(d, "rb").read() + os.chdir("..") + return structure + + +@cache_fn +def wrap_directory(dirp, level=0): + structure = parse_directory(dirp) + data = [] + for k, v in structure.items(): + if isinstance(v, dict): + data.append(wrap_directory(k, level + 1)) + else: + data.append(FileWrap(k, v)) + if level == 0: + os.chdir(os.path.join("..", "..")) + return DirWrap(dirp, *data) + + +@cache_fn +def unpack(dirw: DirWrap): + import shutil + + for k, v in dirw.contains.items(): + if isinstance(v, DirWrap): + if os.path.isdir(k): + shutil.rmtree(k) + os.mkdir(v.name) + os.chdir(v.name) + unpack(v) + os.chdir("..") + else: + open(k, "wb+").write(v.data) diff --git a/lib/keyexchange.py b/lib/keyexchange.py new file mode 100644 index 0000000..72097d3 --- /dev/null +++ b/lib/keyexchange.py @@ -0,0 +1,20 @@ +from cryptography.fernet import Fernet +from random_art.randomart import drunkenwalk, draw +from random import choices +from string import ascii_letters, digits +import os +from .abc import Key + + +def generate_keys(): + key = Fernet.generate_key() + path = os.path.join(".sing", "system", ".keyfile") + hash = "".join(choices(ascii_letters + digits, k=10)) + randomart = draw(drunkenwalk(key), hash) + print(f"The Key is {key}") + print("The Key's randomart is") + print(randomart) + key = Key(path, key, hash, randomart) + key.dump() + print(f"Saved Keys in {path}") + return key diff --git a/sing.py b/sing.py new file mode 100644 index 0000000..38843ad --- /dev/null +++ b/sing.py @@ -0,0 +1,426 @@ +import typer +import os +import typing as t +import shutil +from pathlib import Path +import cson +import getpass +from lib import abc +import string +from lib.db import Database +from lib.dirstat import wrap_directory, unpack + +import socket + +from hooks.remote import remote, clone as _clone +from hooks.diff import diff + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + host = s.getsockname()[0] + s.close() +except: + host = "127.0.0.1" + +app = typer.Typer(name="sing", help="Singularity - the worst Source Control ever") +app.add_typer(remote, name="remote") +app.add_typer(diff, name="diff") + + +@app.command() +def clone(url: str = typer.Argument(...)): + _clone(url, init_fn=init, pull_fn=pull) + + +@app.command() +def init( + dir=typer.Argument("."), + branch: str = typer.Option("master", "-b", "--initial-branch"), + force: bool = typer.Option(False, "-f", "--force", "-r", "--reinit"), +): + """ + Initializes a new Repository, or reinitializes an existing one + """ + initial_cfg = {"current_branch": branch, "branches": [branch], "remotes": {}} + user_cfg = { + "user.name": f"", + "user.email": f"", + "repo.name": os.path.basename(os.path.abspath(dir)), + } + if os.path.exists(os.path.join(dir, ".sing")): + if force: + shutil.rmtree(os.path.join(dir, ".sing")) + print("Reinitializing Singularity Repository...") + else: + return print("Singularity Config Directory already exists - Quitting") + os.mkdir(os.path.join(dir, ".sing")) + os.chdir(os.path.join(dir, ".sing")) + os.mkdir("branches") # Branch Directories + os.mkdir(os.path.join("branches", branch)) # Initial Branch + os.mkdir("stage") # Staged Changes + os.mkdir("system") # Remotes, Branch Config, Local Config, Database + n = open(os.path.join("system", "sing.db"), "wb+") + n.close() + cson.dump(initial_cfg, open(os.path.join("system", "branchcf.cson"), "w+")) + cson.dump(user_cfg, open(os.path.join("system", "localconfig.cson"), "w+")) + os.mkdir("control") # Timeline etc. + os.mkdir("overhead") # Stashed Data + print("Initialized barebones Repository in {0}".format(os.path.abspath(dir))) + + +@app.command() +def config( + key=typer.Argument(None), + list: bool = typer.Option(False, "-l", "--list"), + set: str = typer.Option(None, "--set"), +): + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if key: + if set: + ucfg[key] = set + cson.dump( + ucfg, open(os.path.join(".sing", "system", "localconfig.cson"), "w+") + ) + else: + print(ucfg.get(key, f"Not found: {key}")) + if list: + subpart = {} + for k, v in ucfg.items(): + root, val = k.split(".") + if root not in subpart: + subpart[root] = [] + subpart[root].append((val, v)) + for root, values in subpart.items(): + print(f"- {root}") + for key, value in values: + print(f"---- {key} -> {value}") + + +@app.command() +def log(): + """""" + for commitfile in os.listdir(os.path.join(".sing", "control")): + print(open(os.path.join(".sing", "control", commitfile)).read()) + + +@app.command() +def stash(files: t.List[Path]): + """ + Stashes Files into Overhead to avoid conflicts. Usually called automatically + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "overhead", bn)): + shutil.rmtree(os.path.join(".sing", "overhead", bn)) + shutil.copytree(fp, os.path.join(".sing", "overhead", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "overhead", bn)): + os.remove(os.path.join(".sing", "overhead", bn)) + shutil.copyfile(fp, os.path.join(".sing", "overhead", bn)) + + +@app.command() +def add(files: t.List[Path]): + """ + Stage Files or Directories for a commit + """ + ignore = [".sing"] + if os.path.isfile(".signore"): + ignore.extend(open(".signore").readlines()) + for file in files: + fp = os.path.abspath(file.name) + bn = os.path.basename(fp) + if fp == os.getcwd(): + add(list([Path(i) for i in os.listdir(fp)])) + return + else: + if bn in ignore: + continue + elif os.path.isdir(fp): + if os.path.isdir(os.path.join(".sing", "stage", bn)): + shutil.rmtree(os.path.join(".sing", "stage", bn)) + shutil.copytree(fp, os.path.join(".sing", "stage", bn)) + elif os.path.isfile(fp): + if os.path.isfile(os.path.join(".sing", "stage", bn)): + os.remove(os.path.join(".sing", "stage", bn)) + shutil.copyfile(fp, os.path.join(".sing", "stage", bn)) + + +@app.command() +def rm(files: t.List[str]): + """ + Unstage staged Files + """ + for file in files: + if os.path.exists(os.path.join(".sing", "stage", file)): + if os.path.isdir(os.path.join(".sing", "stage", file)): + shutil.rmtree(os.path.join(".sing", "stage", file)) + else: + os.remove(os.path.join(".sing", "stage", file)) + + +@app.command() +def branch(make_new: str = typer.Option(None, "-m", "--new")): + """ + List Branches or make a new one. To switch, use the 'checkout' command. + """ + if not make_new: + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + for branch in cfg["branches"]: + if branch == cfg["current_branch"]: + print(f"* {branch}") + else: + print(branch) + else: + os.mkdir(os.path.join(".sing", "branches", make_new)) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + cfg["branches"].append(make_new) + cfg["current_branch"] = make_new + cson.dump(cfg, open(os.path.join(".sing", "system", "branchcf.cson"), "w+")) + + +@app.command() +def commit( + message: str = typer.Option(None, "-m", "--message"), + amend: bool = typer.Option(False, "-a", "--amend"), +): + """ + Commit the staged Files and write them to the Database + + Options: + -m, --message : The Commit Message. + -a. --amend : Overwrite the last commit. + """ + if not message: + open(".commit.tmp", "w+") + typer.edit(filename=".commit.tmp") + message = open(".commit.tmp").read() + os.remove(".commit.tmp") + ucfg = cson.load(open(os.path.join(".sing", "system", "localconfig.cson"))) + if ucfg["user.name"] == "" or ucfg["user.email"] == "": + print("*** Please tell me who you are") + print("\nRun\n") + print('\tsing config user.name --set "Your Name" ') + print('\tsing config user.example --set "you@example.com" ') + return + if amend: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))-1}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + else: + try: + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + shutil.rmtree(os.path.join(".sing", "branches", cfg["current_branch"])) + shutil.copytree( + os.path.join(".sing", "stage"), + os.path.join(".sing", "branches", cfg["current_branch"]), + ) + import random + from datetime import datetime + + cwid = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + + commitfile = f""" + commit {cwid} {cfg["current_branch"]} + + Author: {ucfg['user.name']} <{ucfg['user.email']}> + Date: {datetime.now()} + + {message} + """ + commit_data = wrap_directory( + os.path.join(".sing", "branches", cfg["current_branch"]) + ) + db.write(cwid, commit_data) + db.commit() + print(os.getcwd()) + open( + os.path.join( + ".sing", + "control", + f"COMMIT-{len(os.listdir(os.path.join('.sing', 'control')))}.commit", + ), + "w+", + ).write(commitfile) + print(f"Commit Mode +w - On ") + except Exception as e: + import sys, traceback + + print(sys.exc_info()) + print(traceback.format_exc()) + print(e) + + +@app.command() +def stat(): + """ + Print the entire sing Configuration Tree + """ + dw = wrap_directory(".sing") + print(dw.stringify(), end="") + print("local{HEAD}") + + +@app.command() +def slog(): + """ + List all Commits in the Database + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + for k, v in db.data.items(): + print(f"{k} --> {type(v)}") + + +@app.command() +def pull(): + """ + Stash the current Tree and integrate changes downloaded from Remote into active branch + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + stash([Path(".")]) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, id, branch = i.split() + if branch == cfg["current_branch"]: + break + revert(id, branch=branch) + + +@app.command(no_args_is_help=True) +def comtree(c_id: str = typer.Argument(...)): + """ + View the Tree Structure of the Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + print(entry.stringify()) + print("HEAD/") + + +@app.command(no_args_is_help=True) +def revert( + c_id: str = typer.Argument(...), branch: str = typer.Option(None, "-b", "--branch") +): + """ + Reverts to Commit + """ + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + unpack(entry) + + +@app.command(no_args_is_help=True) +def checkout( + branch: str = typer.Argument(...), + force: bool = typer.Option(False, "-f", "--force"), +): + """ + Switch branches or restore working tree files + """ + cfg = cson.load(open(os.path.join(".sing", "system", "branchcf.cson"))) + db = Database.connect(os.path.join(".sing", "system", "sing.db")) + id = len(os.listdir(os.path.join(".sing", "control"))) - 1 + commit_fn = os.path.join(".sing", "control", f"COMMIT-{id}.commit") + dname = open(commit_fn).readlines() + for i in dname: + i = i.lstrip() + if i.startswith("commit"): + _, c_id, _branch = i.split() + if branch == _branch: + break + + entry = db.get(c_id) + if not entry: + return print(f"Fatal -- Cannot find Commit <{c_id}> -- Aborted") + ignore = [".sing"] + + if os.path.exists(".signore"): + ignore += open(".signore").readlines() + if not force: + stash([Path(".")]) + for n in os.listdir(): + if n in ignore: + continue + if os.path.isfile(n): + os.remove(n) + else: + shutil.rmtree(n) + cfg["current_branch"] = branch + cson.dump(cfg, open(os.path.join(".sing", "system", "branchcf.cson"))) + unpack(entry) + + +if __name__ == "__main__": + app() diff --git a/test.py b/test.py new file mode 100644 index 0000000..929db5c --- /dev/null +++ b/test.py @@ -0,0 +1 @@ +# Lol