initial commit
This commit is contained in:
commit
cf661af751
|
@ -0,0 +1,4 @@
|
|||
__pycache__/
|
||||
.sing/
|
||||
.signore/
|
||||
|
|
@ -0,0 +1 @@
|
|||
__pycache__
|
Binary file not shown.
Binary file not shown.
|
@ -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}")
|
|
@ -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 <name> for the repository at <url>. The command 'sing remote fetch' can then
|
||||
be used to create and update remote branches (<name>/<branch>)
|
||||
"""
|
||||
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}]")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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 <C_ID>
|
||||
"""
|
||||
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 <C_ID>
|
||||
"""
|
||||
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()
|
|
@ -0,0 +1 @@
|
|||
# Lol
|
|
@ -0,0 +1 @@
|
|||
__pycache__
|
Binary file not shown.
Binary file not shown.
|
@ -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}")
|
|
@ -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 <name> for the repository at <url>. The command 'sing remote fetch' can then
|
||||
be used to create and update remote branches (<name>/<branch>)
|
||||
"""
|
||||
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}]")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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 <C_ID>
|
||||
"""
|
||||
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 <C_ID>
|
||||
"""
|
||||
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()
|
|
@ -0,0 +1 @@
|
|||
# Lol
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit Td0HEjzAM8FuvnYUOnOTP6p2KTz7v9Ig master
|
||||
|
||||
Author: a <a>
|
||||
Date: 2023-10-16 08:00:22.388514
|
||||
|
||||
feat: Initial Commit
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit pgVDG99ljSQxDWI4GZm5aI0ahtDXcmbU secondary
|
||||
|
||||
Author: Secondary Jane <SecondaryJane@localhost>
|
||||
Date: 2023-10-16 14:27:16.538311
|
||||
|
||||
kewl
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit icG9CADYZ33JNERREpa2fpGYTRA3G93d master
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-16 14:36:34.801126
|
||||
|
||||
Commit
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit zCzv53Xfm4O3gZrz31vj2br4tmWc2mlv master
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-24 16:10:42.542050
|
||||
|
||||
blob
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit x1JPywLQQrB3yYv5Qrd2a4Dg3DFWl5kd master
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-25 07:48:27.216594
|
||||
|
||||
feat: Add checkout
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit YmRvFt2tbeNt7sbybxaCyen3hRkz2YwU foo
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-25 07:51:09.514969
|
||||
|
||||
fix: Improve Checkout Command
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit E1k6zZKOcU0d3815pEwIYcUdXzf0RtPy foo
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-25 07:54:31.541732
|
||||
|
||||
fix: Forgot to update config
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
commit hcfKqMPHotAyoCjDFvRnIyNnFcP2u93U foo
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-26 14:00:13.543435
|
||||
|
||||
None
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
commit L81bNUlwe0XgNi5IHlPa6Ix1HVRs91eO foo
|
||||
|
||||
Author: Jane Johanna Yosef <stupidjane@tutanota.com>
|
||||
Date: 2023-10-26 14:00:41.063254
|
||||
|
||||
Hello World
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
__pycache__
|
Binary file not shown.
Binary file not shown.
|
@ -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}")
|
|
@ -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 <name> for the repository at <url>. The command 'sing remote fetch' can then
|
||||
be used to create and update remote branches (<name>/<branch>)
|
||||
"""
|
||||
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}]")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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 <C_ID>
|
||||
"""
|
||||
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 <C_ID>
|
||||
"""
|
||||
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()
|
|
@ -0,0 +1 @@
|
|||
# Lol
|
|
@ -0,0 +1,5 @@
|
|||
--- BEGIN FERNET CRYPTOGRAPHY KEY ---
|
||||
oHVd1Vltn_kSHazxhcr0Mi3v35EnjzEcncGPDdantVc=
|
||||
s7IyTiiBD4
|
||||
|
||||
--- END FERNET CRYPTOGRAPHY KEY ---
|
|
@ -0,0 +1 @@
|
|||
{"current_branch":"foo","branches":["master","secondary","foo"],"remotes":{"text":"192.168.68.110","upstream":"127.0.0.1"}}
|
|
@ -0,0 +1 @@
|
|||
{"user.name":"Jane Johanna Yosef","user.email":"stupidjane@tutanota.com","repo.name":"singularity"}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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}")
|
|
@ -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 <name> for the repository at <url>. The command 'sing remote fetch' can then
|
||||
be used to create and update remote branches (<name>/<branch>)
|
||||
"""
|
||||
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}]")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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 <C_ID>
|
||||
"""
|
||||
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 <C_ID>
|
||||
"""
|
||||
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()
|
Loading…
Reference in New Issue