machineconfig 1.5__py3-none-any.whl → 1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/__init__.py +8 -5
- machineconfig/jobs/python/check_installations.py +173 -163
- machineconfig/jobs/python/checkout_version.py +117 -0
- machineconfig/jobs/python/create_bootable_media.py +14 -14
- machineconfig/jobs/python/create_zellij_template.py +59 -56
- machineconfig/jobs/python/python_cargo_build_share.py +50 -45
- machineconfig/jobs/python/python_ve_symlink.py +20 -18
- machineconfig/jobs/python/tasks.py +4 -0
- machineconfig/jobs/script_installer/azure_data_studio.py +22 -0
- machineconfig/jobs/script_installer/bypass_paywall.py +23 -0
- machineconfig/jobs/script_installer/code.py +34 -0
- machineconfig/jobs/script_installer/docker_desktop.py +41 -0
- machineconfig/jobs/script_installer/ngrok.py +29 -0
- machineconfig/jobs/{python_linux_installers → script_installer}/skim.py +21 -19
- machineconfig/jobs/script_installer/wezterm.py +34 -0
- machineconfig/profile/create.py +107 -200
- machineconfig/profile/shell.py +127 -0
- machineconfig/scripts/__init__.py +6 -6
- machineconfig/scripts/python/cloud_copy.py +93 -0
- machineconfig/scripts/python/cloud_manager.py +38 -0
- machineconfig/scripts/python/cloud_mount.py +115 -52
- machineconfig/scripts/python/cloud_repo_sync.py +154 -114
- machineconfig/scripts/python/cloud_sync.py +261 -79
- machineconfig/scripts/python/croshell.py +151 -0
- machineconfig/scripts/python/devops.py +119 -87
- machineconfig/scripts/python/devops_add_identity.py +27 -23
- machineconfig/scripts/python/devops_add_ssh_key.py +70 -55
- machineconfig/scripts/python/devops_backup_retrieve.py +52 -46
- machineconfig/scripts/python/devops_devapps_install.py +120 -91
- machineconfig/scripts/python/devops_update_repos.py +82 -68
- machineconfig/scripts/python/dotfile.py +42 -38
- machineconfig/scripts/python/fire_jobs.py +351 -98
- machineconfig/scripts/python/ftpx.py +82 -0
- machineconfig/scripts/python/mount_nfs.py +54 -3
- machineconfig/scripts/python/mount_nw_drive.py +31 -0
- machineconfig/scripts/python/mount_ssh.py +44 -20
- machineconfig/scripts/python/onetimeshare.py +60 -51
- machineconfig/scripts/python/pomodoro.py +41 -37
- machineconfig/scripts/python/repos.py +195 -128
- machineconfig/scripts/python/scheduler.py +52 -0
- machineconfig/scripts/python/snapshot.py +21 -21
- machineconfig/scripts/python/start_slidev.py +104 -0
- machineconfig/scripts/python/start_terminals.py +97 -0
- machineconfig/scripts/python/wifi_conn.py +90 -71
- machineconfig/scripts/python/{transfer_wsl_win.py → wsl_windows_transfer.py} +47 -39
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +44 -48
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +136 -130
- machineconfig/utils/installer.py +251 -0
- machineconfig/utils/procs.py +114 -64
- machineconfig/utils/scheduling.py +188 -0
- machineconfig/utils/utils.py +353 -249
- machineconfig/utils/ve.py +222 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/METADATA +140 -110
- machineconfig-1.8.dist-info/RECORD +70 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/WHEEL +1 -1
- machineconfig/jobs/python/python_linux_installers_all.py +0 -73
- machineconfig/jobs/python/python_ve_installer.py +0 -73
- machineconfig/jobs/python/python_windows_installers_all.py +0 -23
- machineconfig/jobs/python_generic_installers/archive/nvim.py +0 -15
- machineconfig/jobs/python_generic_installers/archive/strongbox.py +0 -32
- machineconfig/jobs/python_generic_installers/archive/vtm.py +0 -25
- machineconfig/jobs/python_generic_installers/broot.py +0 -39
- machineconfig/jobs/python_generic_installers/browsh.py +0 -25
- machineconfig/jobs/python_generic_installers/bw.py +0 -26
- machineconfig/jobs/python_generic_installers/chatgpt.py +0 -24
- machineconfig/jobs/python_generic_installers/cpufetch.py +0 -23
- machineconfig/jobs/python_generic_installers/delta.py +0 -59
- machineconfig/jobs/python_generic_installers/dev/__init__.py +0 -0
- machineconfig/jobs/python_generic_installers/dev/autogpt.py +0 -28
- machineconfig/jobs/python_generic_installers/dev/bw.py +0 -29
- machineconfig/jobs/python_generic_installers/dev/evcxr.py +0 -22
- machineconfig/jobs/python_generic_installers/dev/kondo.py +0 -21
- machineconfig/jobs/python_generic_installers/dev/lvim.py +0 -60
- machineconfig/jobs/python_generic_installers/dev/ngrok.py +0 -35
- machineconfig/jobs/python_generic_installers/dev/opencommit.py +0 -21
- machineconfig/jobs/python_generic_installers/dev/qrcp.py +0 -25
- machineconfig/jobs/python_generic_installers/dev/qrscan.py +0 -16
- machineconfig/jobs/python_generic_installers/dev/rust-analyzer.py +0 -24
- machineconfig/jobs/python_generic_installers/dev/termscp.py +0 -23
- machineconfig/jobs/python_generic_installers/dev/tldr.py +0 -25
- machineconfig/jobs/python_generic_installers/dev/tokei.py +0 -24
- machineconfig/jobs/python_generic_installers/diskonaut.py +0 -26
- machineconfig/jobs/python_generic_installers/dua.py +0 -21
- machineconfig/jobs/python_generic_installers/evcxr.py +0 -21
- machineconfig/jobs/python_generic_installers/gitui.py +0 -23
- machineconfig/jobs/python_generic_installers/gopass.py +0 -19
- machineconfig/jobs/python_generic_installers/helix.py +0 -45
- machineconfig/jobs/python_generic_installers/kondo.py +0 -20
- machineconfig/jobs/python_generic_installers/lf.py +0 -25
- machineconfig/jobs/python_generic_installers/lvim.py +0 -34
- machineconfig/jobs/python_generic_installers/mprocs.py +0 -20
- machineconfig/jobs/python_generic_installers/navi.py +0 -20
- machineconfig/jobs/python_generic_installers/ots.py +0 -26
- machineconfig/jobs/python_generic_installers/ouch.py +0 -25
- machineconfig/jobs/python_generic_installers/pomodoro.py +0 -19
- machineconfig/jobs/python_generic_installers/procs.py +0 -20
- machineconfig/jobs/python_generic_installers/qrcp.py +0 -22
- machineconfig/jobs/python_generic_installers/qrscan.py +0 -14
- machineconfig/jobs/python_generic_installers/rclone.py +0 -21
- machineconfig/jobs/python_generic_installers/rust-analyzer.py +0 -24
- machineconfig/jobs/python_generic_installers/tere.py +0 -26
- machineconfig/jobs/python_generic_installers/termscp.py +0 -23
- machineconfig/jobs/python_generic_installers/tldr.py +0 -24
- machineconfig/jobs/python_generic_installers/tokei.py +0 -21
- machineconfig/jobs/python_generic_installers/vtm.py +0 -26
- machineconfig/jobs/python_generic_installers/watchexec.py +0 -21
- machineconfig/jobs/python_linux_installers/archive/__init__.py +0 -0
- machineconfig/jobs/python_linux_installers/archive/ranger.py +0 -18
- machineconfig/jobs/python_linux_installers/bandwhich.py +0 -11
- machineconfig/jobs/python_linux_installers/bottom.py +0 -17
- machineconfig/jobs/python_linux_installers/btop.py +0 -17
- machineconfig/jobs/python_linux_installers/dev/bandwhich.py +0 -13
- machineconfig/jobs/python_linux_installers/dev/bytehound.py +0 -20
- machineconfig/jobs/python_linux_installers/dev/nnn.py +0 -21
- machineconfig/jobs/python_linux_installers/gotty.py +0 -16
- machineconfig/jobs/python_linux_installers/joshuto.py +0 -28
- machineconfig/jobs/python_linux_installers/mcfly.py +0 -12
- machineconfig/jobs/python_linux_installers/nnn.py +0 -18
- machineconfig/jobs/python_linux_installers/topgrade.py +0 -15
- machineconfig/jobs/python_linux_installers/viu.py +0 -19
- machineconfig/jobs/python_linux_installers/xplr.py +0 -22
- machineconfig/jobs/python_linux_installers/zellij.py +0 -31
- machineconfig/jobs/python_windows_installers/archive/ntop.py +0 -20
- machineconfig/jobs/python_windows_installers/bat.py +0 -16
- machineconfig/jobs/python_windows_installers/boxes.py +0 -19
- machineconfig/jobs/python_windows_installers/bypass_paywall.py +0 -18
- machineconfig/jobs/python_windows_installers/dev/bypass_paywall.py +0 -21
- machineconfig/jobs/python_windows_installers/dev/obs_background_removal_plugin.py +0 -20
- machineconfig/jobs/python_windows_installers/fd.py +0 -17
- machineconfig/jobs/python_windows_installers/fzf.py +0 -19
- machineconfig/jobs/python_windows_installers/obs_background_removal_plugin.py +0 -19
- machineconfig/jobs/python_windows_installers/rg.py +0 -15
- machineconfig/jobs/python_windows_installers/ugrep.py +0 -14
- machineconfig/jobs/python_windows_installers/zoomit.py +0 -20
- machineconfig/jobs/python_windows_installers/zoxide.py +0 -20
- machineconfig/profile/fix_shell_profiles.py +0 -8
- machineconfig/scripts/python/archive/__init__.py +0 -0
- machineconfig/scripts/python/archive/bu_gdrive_rx.py +0 -41
- machineconfig/scripts/python/archive/bu_gdrive_sx.py +0 -40
- machineconfig/scripts/python/archive/bu_onedrive_rx.py +0 -59
- machineconfig/scripts/python/archive/bu_onedrive_sx.py +0 -45
- machineconfig/scripts/python/chatgpt.py +0 -28
- machineconfig/scripts/python/choose_ohmybash_theme.py +0 -25
- machineconfig/scripts/python/choose_ohmyposh_theme.py +0 -40
- machineconfig/scripts/python/cloud_rx.py +0 -42
- machineconfig/scripts/python/cloud_sx.py +0 -40
- machineconfig/scripts/python/ftprx.py +0 -37
- machineconfig/scripts/python/ftpsx.py +0 -36
- machineconfig/scripts/python/im2text.py +0 -15
- machineconfig/scripts/python/tmate_conn.py +0 -28
- machineconfig/scripts/python/tmate_start.py +0 -31
- machineconfig/utils/to_exe.py +0 -7
- machineconfig-1.5.dist-info/RECORD +0 -147
- /machineconfig/jobs/{python_generic_installers/archive → script_installer}/__init__.py +0 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
|
|
2
|
+
"""Mount a remote SSHFS share on a local directory
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from platform import system
|
|
7
|
+
from crocodile.meta import SSH, Terminal
|
|
8
|
+
from crocodile.file_management import P
|
|
9
|
+
|
|
10
|
+
from machineconfig.utils.utils import PROGRAM_PATH, choose_ssh_host
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
|
|
15
|
+
print(f"Mounting SSHFS ... ")
|
|
16
|
+
share_info = input("share path? (e.g. user@host:/path) [press enter for interactive choice] = ")
|
|
17
|
+
if share_info == "":
|
|
18
|
+
tmp = choose_ssh_host(multi=False)
|
|
19
|
+
assert isinstance(tmp, str)
|
|
20
|
+
ssh = SSH(host=tmp)
|
|
21
|
+
share_info = f"{ssh.username}@{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_ssh"
|
|
22
|
+
else:
|
|
23
|
+
ssh = SSH(share_info.split(":")[0])
|
|
24
|
+
print(Terminal().run("net use", shell="powershell").op)
|
|
25
|
+
driver_letter = input(r"Choose driver letter (e.g. Z:\) (avoid the ones already used) : ") or "Z:\\"
|
|
26
|
+
|
|
27
|
+
mount_point = input(f"Enter the mount point directory (ex: /mnt/network) [default: ~/data/mount_ssh/{ssh.hostname}]: ")
|
|
28
|
+
if mount_point == "": mount_point = P.home().joinpath(fr"data/mount_ssh/{ssh.hostname}")
|
|
29
|
+
|
|
30
|
+
if system() == "Linux":
|
|
31
|
+
txt = fr"""
|
|
32
|
+
sshfs alex@:/media/dbhdd /media/dbhdd\
|
|
33
|
+
"""
|
|
34
|
+
elif system() == "Windows":
|
|
35
|
+
txt = fr"""
|
|
36
|
+
net use {driver_letter} {share_info}
|
|
37
|
+
fusermount -u /mnt/dbhdd
|
|
38
|
+
"""
|
|
39
|
+
else: raise ValueError(f"Not implemented for this system {system()}")
|
|
40
|
+
PROGRAM_PATH.write_text(txt)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == '__main__':
|
|
44
|
+
pass
|
|
@@ -1,51 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
import base64
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
|
|
2
|
+
"""Share
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# as per https://github.com/Luzifer/ots
|
|
6
|
+
# this script is pure python and connects to server to create a secret
|
|
7
|
+
# or use ots executable to do this job
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
import crocodile.file_management as fm
|
|
12
|
+
from crocodile.core import install_n_import
|
|
13
|
+
import base64
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def encrypt(key: str, pwd: str):
|
|
18
|
+
install_n_import(library="Crypto.Cipher", package="pycryptodome")
|
|
19
|
+
# from Crypto.Cipher import AES
|
|
20
|
+
# from Crypto.Hash import MD5
|
|
21
|
+
AES = __import__("Crypto", fromlist=["Cipher"]).Cipher.AES
|
|
22
|
+
MD5 = __import__("Crypto", fromlist=["Hash"]).Hash.MD5
|
|
23
|
+
pwd_enc = pwd.encode("utf-8")
|
|
24
|
+
hash_object = MD5.new(key.encode("utf-8"))
|
|
25
|
+
key_digest = hash_object.digest()
|
|
26
|
+
iv = b'\x00' * 16
|
|
27
|
+
cipher = AES.new(key_digest, AES.MODE_CBC, iv)
|
|
28
|
+
pad = 16 - (len(pwd_enc) % 16)
|
|
29
|
+
pwd_enc += bytes([pad] * pad)
|
|
30
|
+
encrypted_password = cipher.encrypt(pwd_enc)
|
|
31
|
+
return base64.b64encode(encrypted_password).decode("utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def share(secret: str, password: Optional[str]):
|
|
35
|
+
if password is not None: encoded_secret = encrypt(password, secret)
|
|
36
|
+
else: encoded_secret = secret
|
|
37
|
+
|
|
38
|
+
url = "https://ots.fyi/api/create"
|
|
39
|
+
|
|
40
|
+
payload = {"secret": encoded_secret}
|
|
41
|
+
headers = {'Content-Type': 'application/json'}
|
|
42
|
+
|
|
43
|
+
response = requests.post(url, json=payload, headers=headers, timeout=10)
|
|
44
|
+
|
|
45
|
+
if response.status_code == 201:
|
|
46
|
+
res = response.json()
|
|
47
|
+
print(res)
|
|
48
|
+
assert res["success"] is True, "Request have failed"
|
|
49
|
+
share_url = fm.P(f"https://ots.fyi/#{res['secret_id']}") + (f"|{password}" if password is not None else "")
|
|
50
|
+
print(repr(share_url))
|
|
51
|
+
return share_url
|
|
52
|
+
else:
|
|
53
|
+
print("Request failed")
|
|
54
|
+
raise RuntimeError(response.text)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
sc = input("Secret: ")
|
|
59
|
+
pwdd = input("Password: ") or None
|
|
60
|
+
share(secret=sc, password=pwdd)
|
|
@@ -1,37 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# parser.add_argument("
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
|
|
2
|
+
"""Pomodoro
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from crocodile.toolbox import Log, install_n_import, Scheduler, P
|
|
6
|
+
import time
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def pomodoro(work: int = 25, rest: int = 5, repeats: int = 4):
|
|
11
|
+
logger = Log(name="pomodoro", file=False, stream=True)
|
|
12
|
+
def loop(sched: Scheduler):
|
|
13
|
+
speak("Alright, time to start working..."); start = datetime.now(); _ = sched
|
|
14
|
+
while (diff := work - ((datetime.now() - start).seconds / 60)) > 0: logger.debug(f"Keep working. Time Left: {round(diff)} minutes"); time.sleep(60 * 1)
|
|
15
|
+
speak("Now, its time to take a break."); start = datetime.now()
|
|
16
|
+
while (diff := rest - ((datetime.now() - start).seconds / 60)) > 0: logger.critical(f"Keep Resting. Time Left: {round(diff)} minutes"); time.sleep(60 * 1)
|
|
17
|
+
def speak(txt: str):
|
|
18
|
+
install_n_import("gtts").gTTS(txt, lang='en', tld='com.au').save(tmp := P.tmpfile(suffix=".mp3")); time.sleep(0.5)
|
|
19
|
+
pyglet = install_n_import("pyglet"); pyglet.resource.path = [tmp.parent.str]; pyglet.resource.reindex(); pyglet.resource.media(tmp.name).play()
|
|
20
|
+
def beep(duration: int = 1, frequency: int = 3000):
|
|
21
|
+
try: import winsound
|
|
22
|
+
except ImportError: __import__("os").system(f'beep -f {frequency} -l {1000 * duration}')
|
|
23
|
+
else: winsound.Beep(frequency, 1000 * duration) # type: ignore
|
|
24
|
+
_ = beep
|
|
25
|
+
return Scheduler(routine=loop, max_cycles=repeats, logger=logger, wait="0.1m").run()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# def main():
|
|
29
|
+
# parser = argparse.ArgumentParser(description='FTP client')
|
|
30
|
+
#
|
|
31
|
+
# parser.add_argument("machine", help=f"machine ssh address", default="")
|
|
32
|
+
# parser.add_argument("file", help="file/folder path.", default="")
|
|
33
|
+
# # FLAGS
|
|
34
|
+
# parser.add_argument("--recursive", "-r", help="Send recursively.", action="store_true") # default is False
|
|
35
|
+
# parser.add_argument("--zipFirst", "-z", help="Zip before sending.", action="store_true") # default is False
|
|
36
|
+
#
|
|
37
|
+
# args = parser.parse_args()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
pomodoro()
|
|
@@ -1,128 +1,195 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from rich import print
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
program =
|
|
43
|
-
echo
|
|
44
|
-
''
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if
|
|
128
|
-
|
|
1
|
+
|
|
2
|
+
"""Repos
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rich import print as pprint
|
|
6
|
+
from machineconfig.utils.utils import write_shell_script, CONFIG_PATH, DEFAULTS_PATH
|
|
7
|
+
from crocodile.file_management import P, install_n_import, Read, Save
|
|
8
|
+
from crocodile.core import randstr
|
|
9
|
+
import argparse
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Optional, Any
|
|
13
|
+
# tm = Terminal()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GitAction(Enum):
|
|
17
|
+
commit = "commit"
|
|
18
|
+
push = "push"
|
|
19
|
+
pull = "pull"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class RepoRecord:
|
|
24
|
+
name: str
|
|
25
|
+
parent_dir: str
|
|
26
|
+
remotes: list[str]
|
|
27
|
+
version: dict[str, str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def git_action(path: P, action: GitAction, mess: Optional[str] = None, r: bool = False) -> str:
|
|
31
|
+
from git.exc import InvalidGitRepositoryError
|
|
32
|
+
from git.repo import Repo
|
|
33
|
+
try:
|
|
34
|
+
repo = Repo(str(path), search_parent_directories=False)
|
|
35
|
+
except InvalidGitRepositoryError:
|
|
36
|
+
pprint(f"Skipping {path} because it is not a git repository.")
|
|
37
|
+
if r:
|
|
38
|
+
prgs = [git_action(path=sub_path, action=action, mess=mess, r=r) for sub_path in path.search()]
|
|
39
|
+
return "\n".join(prgs)
|
|
40
|
+
else: return "\necho 'skipped because not a git repo'\n\n"
|
|
41
|
+
|
|
42
|
+
program = f'''
|
|
43
|
+
echo '>>>>>>>>> {action}'
|
|
44
|
+
cd '{path}'
|
|
45
|
+
'''
|
|
46
|
+
if action == GitAction.commit:
|
|
47
|
+
if mess is None: mess = "auto_commit_" + randstr()
|
|
48
|
+
program += f'''
|
|
49
|
+
git add .; git commit -am "{mess}"
|
|
50
|
+
'''
|
|
51
|
+
if action == GitAction.push or action == GitAction.pull:
|
|
52
|
+
# remotes = tm.run(f"cd {path}; git remote", shell="powershell").op.split("\n")
|
|
53
|
+
action_name = "pull" if action == GitAction.pull else "push"
|
|
54
|
+
cmds = [f'echo "pulling from {remote.url}" ; git {action_name} {remote.name} {repo.active_branch.name}' for remote in repo.remotes]
|
|
55
|
+
program += '\n' + '\n'.join(cmds) + '\n'
|
|
56
|
+
program = program + f'''
|
|
57
|
+
echo ""; echo ""
|
|
58
|
+
'''
|
|
59
|
+
return program
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main():
|
|
63
|
+
parser = argparse.ArgumentParser(description='REPO MANAGER')
|
|
64
|
+
# POSITIONAL
|
|
65
|
+
parser.add_argument("directory", help="folder containing repos to record a json out of OR a specs json file to follow.", default="")
|
|
66
|
+
# FLAGS
|
|
67
|
+
parser.add_argument("--push", help=f"push", action="store_true")
|
|
68
|
+
parser.add_argument("--pull", help=f"pull", action="store_true")
|
|
69
|
+
parser.add_argument("--commit", help=f"commit", action="store_true")
|
|
70
|
+
parser.add_argument("--all", help=f"pull, commit and push", action="store_true")
|
|
71
|
+
parser.add_argument("--record", help=f"record respos", action="store_true")
|
|
72
|
+
parser.add_argument("--clone", help=f"clone repos from record", action="store_true")
|
|
73
|
+
parser.add_argument("--checkout", help=f"Check out to versions prvided in this json file", action="store_true")
|
|
74
|
+
parser.add_argument("--checkout_to_branch", help="Checkout to the main branch", action="store_true")
|
|
75
|
+
parser.add_argument("--recursive", "-r", help=f"recursive flag", action="store_true")
|
|
76
|
+
# OPTIONAL
|
|
77
|
+
parser.add_argument("--cloud", "-c", help=f"cloud", default=None)
|
|
78
|
+
args = parser.parse_args()
|
|
79
|
+
|
|
80
|
+
if args.directory == "": repos_root = P.home().joinpath("code") # it is a positional argument, can never be empty.
|
|
81
|
+
else: repos_root = P(args.directory).expanduser().absolute()
|
|
82
|
+
_ = install_n_import("git", "gitpython")
|
|
83
|
+
|
|
84
|
+
program = ""
|
|
85
|
+
if args.record:
|
|
86
|
+
res = record_repos(repos_root=str(repos_root))
|
|
87
|
+
pprint(f"Recorded repositories:\n", res)
|
|
88
|
+
save_path = CONFIG_PATH.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
|
|
89
|
+
# Save.pickle(obj=res, path=save_path)
|
|
90
|
+
Save.json(obj=res, path=save_path)
|
|
91
|
+
pprint(f"Result pickled at {P(save_path)}")
|
|
92
|
+
if args.cloud is not None: P(save_path).to_cloud(rel2home=True, cloud=args.cloud)
|
|
93
|
+
program += f"""\necho '>>>>>>>>> Finished Recording'\n"""
|
|
94
|
+
elif args.clone or args.checkout or args.checkout_to_branch:
|
|
95
|
+
# preferred_remote = input("Enter preferred remote to use (default: None): ") or ""
|
|
96
|
+
program += f"""\necho '>>>>>>>>> Cloning Repos'\n"""
|
|
97
|
+
if not repos_root.exists() or repos_root.stem != 'repos.json': # user didn't pass absolute path to pickle file, but rather expected it to be in the default save location
|
|
98
|
+
repos_root = CONFIG_PATH.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
|
|
99
|
+
if not repos_root.exists():
|
|
100
|
+
if args.cloud is None:
|
|
101
|
+
cloud: str = Read.ini(DEFAULTS_PATH)['general']['rclone_config_name']
|
|
102
|
+
print(f"⚠️ Using default cloud: {cloud}")
|
|
103
|
+
else:
|
|
104
|
+
cloud = args.cloud
|
|
105
|
+
assert cloud is not None, f"Path {repos_root} does not exist and cloud was not passed. You can't clone without one of them."
|
|
106
|
+
repos_root.from_cloud(cloud=cloud, rel2home=True)
|
|
107
|
+
assert (repos_root.exists() and repos_root.name == 'repos.json') or args.cloud is not None, f"Path {repos_root} does not exist and cloud was not passed. You can't clone without one of them."
|
|
108
|
+
program += install_repos(specs_path=str(repos_root), clone=args.clone, checkout_to_recorded_commit=args.checkout, checkout_to_branch=args.checkout_to_branch)
|
|
109
|
+
# elif args.checkout is not None:
|
|
110
|
+
|
|
111
|
+
elif args.all or args.commit or args.pull or args.push:
|
|
112
|
+
for a_path in repos_root.search("*"):
|
|
113
|
+
program += f"""echo "{("Handling " + str(a_path)).center(80, "-")}" """
|
|
114
|
+
if args.pull or args.all: program += git_action(path=a_path, action=GitAction.pull, r=args.recursive)
|
|
115
|
+
if args.commit or args.all: program += git_action(a_path, action=GitAction.commit, r=args.recursive)
|
|
116
|
+
if args.push or args.all: program += git_action(a_path, action=GitAction.push, r=args.recursive)
|
|
117
|
+
else: program = "echo 'no action specified, try to pass --push, --pull, --commit or --all'"
|
|
118
|
+
write_shell_script(program, "Script to update repos")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def record_repos(repos_root: str, r: bool = True) -> list[dict[str, Any]]:
|
|
122
|
+
path_obj = P(repos_root).expanduser().absolute()
|
|
123
|
+
if path_obj.is_file(): return []
|
|
124
|
+
search_res = path_obj.search("*", files=False)
|
|
125
|
+
res: list[dict[str, Any]] = []
|
|
126
|
+
for a_search_res in search_res:
|
|
127
|
+
if a_search_res.joinpath(".git").exists():
|
|
128
|
+
res.append(record_a_repo(a_search_res))
|
|
129
|
+
else:
|
|
130
|
+
if r: res += record_repos(str(a_search_res), r=r)
|
|
131
|
+
return res
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def record_a_repo(path: P, search_parent_directories: bool = False, preferred_remote: Optional[str] = None):
|
|
135
|
+
from git.repo import Repo
|
|
136
|
+
repo = Repo(path, search_parent_directories=search_parent_directories) # get list of remotes using git python
|
|
137
|
+
repo_root = P(repo.working_dir).absolute()
|
|
138
|
+
remotes = {remote.name: remote.url for remote in repo.remotes}
|
|
139
|
+
if preferred_remote is not None:
|
|
140
|
+
if preferred_remote in remotes: remotes = {preferred_remote: remotes[preferred_remote]}
|
|
141
|
+
else:
|
|
142
|
+
print(f"⚠️ `{preferred_remote=}` not found in {remotes}.")
|
|
143
|
+
preferred_remote = None
|
|
144
|
+
try: commit = repo.head.commit.hexsha
|
|
145
|
+
except ValueError: # look at https://github.com/gitpython-developers/GitPython/issues/1016
|
|
146
|
+
print(f"⚠️ Failed to get latest commit of {repo}")
|
|
147
|
+
# cmd = "git config --global -add safe.directory"
|
|
148
|
+
commit = None
|
|
149
|
+
try: current_branch = repo.head.reference.name # same as repo.active_branch.name
|
|
150
|
+
except TypeError:
|
|
151
|
+
print(f"⁉️ Failed to get current branch of {repo}. It is probably in a detached state.")
|
|
152
|
+
current_branch = None
|
|
153
|
+
res: dict[str, Any] = {"name": repo_root.name, "parent_dir": repo_root.parent.collapseuser().as_posix(),
|
|
154
|
+
"current_branch": current_branch,
|
|
155
|
+
"remotes": remotes, "version": {"branch": current_branch, "commit": commit}}
|
|
156
|
+
return res
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def install_repos(specs_path: str, clone: bool = True, checkout_to_recorded_commit: bool = False, checkout_to_branch: bool = False, editable_install: bool = False, preferred_remote: Optional[str] = None):
|
|
160
|
+
program = ""
|
|
161
|
+
path_obj = P(specs_path).expanduser().absolute()
|
|
162
|
+
repos: list[dict[str, Any]] = Read.json(path_obj)
|
|
163
|
+
for repo in repos:
|
|
164
|
+
parent_dir = P(repo["parent_dir"]).expanduser().absolute().create()
|
|
165
|
+
for idx, (remote_name, remote_url) in enumerate(repo["remotes"].items()):
|
|
166
|
+
if clone:
|
|
167
|
+
if idx == 0: # clone
|
|
168
|
+
if preferred_remote is not None:
|
|
169
|
+
if preferred_remote in repo["remotes"]:
|
|
170
|
+
remote_name = preferred_remote
|
|
171
|
+
remote_url: str = repo["remotes"][preferred_remote]
|
|
172
|
+
else:
|
|
173
|
+
print(f"⚠️ `{preferred_remote=}` not found in {repo['remotes']}.")
|
|
174
|
+
# preferred_remote = None
|
|
175
|
+
program += f"\ncd {parent_dir.collapseuser().as_posix()}; git clone {remote_url} --origin {remote_name}"
|
|
176
|
+
program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git remote set-url {remote_name} {remote_url}"
|
|
177
|
+
# the new url-setting to ensure that account name before `@` was not lost (git clone ignores it): https://thisismygitrepo@github.com/thisismygitrepo/crocodile.git
|
|
178
|
+
program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git remote add {remote_name} {remote_url}"
|
|
179
|
+
if checkout_to_recorded_commit:
|
|
180
|
+
commit = repo['version']['commit']
|
|
181
|
+
if isinstance(commit, str): program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git checkout {commit}"
|
|
182
|
+
else: print(f"Skipping {repo['parent_dir']} because it doesn't have a commit recorded. Found {commit}")
|
|
183
|
+
break # you need to checkout only once, no need to repeat for other remotes.
|
|
184
|
+
if checkout_to_branch:
|
|
185
|
+
program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git checkout {repo['current_branch']}"
|
|
186
|
+
break
|
|
187
|
+
if editable_install and idx == 0:
|
|
188
|
+
program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; pip install -e ."
|
|
189
|
+
program += "\n"
|
|
190
|
+
pprint(program)
|
|
191
|
+
return program
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == '__main__':
|
|
195
|
+
main()
|