Skip to content

Instantly share code, notes, and snippets.

@jboelter
Last active December 12, 2020 19:49
Show Gist options
  • Save jboelter/d7672485b90fad455c4874e437269857 to your computer and use it in GitHub Desktop.
Save jboelter/d7672485b90fad455c4874e437269857 to your computer and use it in GitHub Desktop.
Clone a git repo with the same path structure on disk
#! /usr/bin/python3
# Usage:
# clone repo_url [path] [clone args]
#
# Examples
# clone https://github.com/username/reponame.git
# git clone https://github.com/username/reponame.git /home/user/src/github.com/username/reponame
#
# clone https://github.com/username/reponame.git /src
# git clone https://github.com/username/reponame.git /src/github.com/username/reponame
#
#
import os
import re
import sys
import subprocess
def usage():
script = sys.argv[0].split(os.path.sep)[-1]
print("Usage:")
print(f"\t{script} repo_url [path] [clone args]")
print()
print("Examples")
print("\tclone https://github.com/username/reponame.git")
print("\tgit clone https://github.com/username/reponame.git /home/user/src/github.com/username/reponame")
print()
print("\tclone https://github.com/username/reponame.git /src")
print("\tgit clone https://github.com/username/reponame.git /src/github.com/username/reponame")
print()
print("\tclone https://github.com/username/reponame.git ~/src --depth 1")
print("\tgit clone --depth 1 https://github.com/username/reponame.git /src/github.com/username/reponame")
sys.exit(0)
class _Getch:
"""Gets a single character from standard input. Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self):
char = self.impl()
if char == '\x03':
raise KeyboardInterrupt
elif char == '\x04':
raise EOFError
return char
class _GetchUnix:
def __init__(self):
import tty
import sys
def __call__(self):
import sys
import tty
import termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
test_repos = [
# supported
('ssh://[email protected]:1234/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('ssh://[email protected]/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('ssh://host.xz:1234/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('ssh://host.xz/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('ssh://[email protected]/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('ssh://host.xz/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('ssh://[email protected]/~user/path/to/repo.git/', '/test/path/src/host.xz/~user/path/to/repo'),
('ssh://host.xz/~user/path/to/repo.git/', '/test/path/src/host.xz/~user/path/to/repo'),
('ssh://[email protected]/~/path/to/repo.git', '/test/path/src/host.xz/~/path/to/repo'),
('ssh://host.xz/~/path/to/repo.git', '/test/path/src/host.xz/~/path/to/repo'),
('rsync://host.xz/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('git://host.xz/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('git://host.xz/~user/path/to/repo.git/', '/test/path/src/host.xz/~user/path/to/repo'),
('http://host.xz/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('https://host.xz/path/to/repo.git/', '/test/path/src/host.xz/path/to/repo'),
('/path/to/repo.git/', '/test/path/src/path/to/repo'),
('path/to/repo.git/', '/test/path/src/path/to/repo'),
('~/path/to/repo.git', '/test/path/src/~/path/to/repo'),
('file:///path/to/repo.git/', '/test/path/src/path/to/repo'),
('file://~/path/to/repo.git/', '/test/path/src/~/path/to/repo'),
# unsupported
('[email protected]:/path/to/repo.git/',''),
('host.xz:/path/to/repo.git/',''),
('[email protected]:~user/path/to/repo.git/',''),
('host.xz:~user/path/to/repo.git/',''),
('[email protected]:path/to/repo.git',''),
('host.xz:path/to/repo.git',''),
]
def yes_or_no(question):
getch = _Getch()
try:
while True:
print(f"{question} [Y/n]")
char = getch()
if char == 'Y' or char == 'y' or char == '\r':
return True
if char == 'N' or char == 'n' or char == '\x1b':
return False
except KeyboardInterrupt:
return False
except EOFError:
return False
def get_path(repo_url, root):
rx = re.compile(r'^(?P<protocol>(git|file|rsync|ssh|http(s)?):///?)?(?P<user>\w+@)?(?P<host>[^:]+)(:\d+)?/(?P<path>.*)\.git/?$')
m = rx.match(repo_url)
if m is None:
return ''
# print(f"repo: {repo_url}")
# print(f"prot: {m.group('protocol')}")
# print(f"user: {m.group('user')}")
# print(f"host: {m.group('host')}")
# print(f"path: {m.group('path')}")
path = m.group('host') + "/" + m.group('path')
path = path[1:] if len(path) > 0 and path[0] == '/' else path
path_out = os.path.abspath(os.path.expandvars(root))
path_out = os.path.abspath(os.path.join(path_out, path))
return path_out
def main():
if len(sys.argv) == 1:
usage()
if sys.argv[1] == "test":
failed = False
for repo in test_repos:
path = get_path(repo[0], '/test/path/src')
result = True if path == repo[1] else False
print(f"{'PASS' if result else 'FAIL'} {repo} expects: {path}")
failed = True if result is False else failed
sys.exit(1 if failed else 0)
repo_url = sys.argv[1]
dir = sys.argv[2] if len(sys.argv) > 2 else '$HOME/src'
args = tuple(sys.argv[3:])
path = get_path(repo_url, dir)
if path is None or path == '':
print("unsupported repo url")
sys.exit(1)
git_cmd = ("git", "clone") + args + (repo_url, path)
if yes_or_no(' '.join(git_cmd)):
git_pid = subprocess.call(git_cmd)
sys.exit(git_pid)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment