| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- import subprocess
- import pathlib
- import time
- import sys
- REPO_ROOT = pathlib.Path(__file__).parent.parent
- def git(*args, pipe=False, tee=False):
- if tee:
- result = b""
- line = b""
- with subprocess.Popen(
- ["git", *args],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- bufsize=0,
- ) as proc:
- cr = False
- while True:
- char = proc.stdout.read(1)
- if not char:
- result += line
- break
- sys.stdout.write(char.decode())
- # Simulate terminal line buffering, where \r resets to start of line overwriting what is already there
- # unless \n follows it, which is just going to next line keeping the content on previous one
- # Useful for terminal progress bars/numbers, no need to log all this data as separate lines
- if char == b"\r":
- cr = True
- continue
- elif cr:
- cr = False
- if char != b"\n":
- line = b""
- line += char
- if char == b"\n":
- result += line
- line = b""
- return result.decode().removesuffix("\n"), proc.returncode
- elif pipe:
- return subprocess.check_output(["git", *args], text=True).removesuffix("\n")
- else:
- return subprocess.check_call(["git", *args])
- def is_workdir_clean():
- try:
- git("diff", "--quiet")
- git("diff", "--cached", "--quiet")
- git("merge", "HEAD", pipe=True)
- return True
- except subprocess.CalledProcessError:
- return False
- def send_alert(title, message):
- try:
- subprocess.run(
- ["notify-send", "-a", "Git", "-i", "git", title, message],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- except Exception:
- pass
- def check_merge_result(path, repo, result, status):
- if "Automatic merge failed; fix conflicts and then commit the result." in result:
- print(f"MERGE_MSG: Merge {path} from {repo}")
- send_alert("Subtree merge failed", "Resolve current index to continue")
- while True:
- input("Resolve current index then press Enter...")
- if is_workdir_clean():
- break
- time.sleep(1)
- elif (
- "Please commit your changes or stash them before you switch branches." in result
- ):
- sys.exit(1)
- elif "fatal: " in result:
- sys.exit(1)
- elif status != 0:
- print(f"Git returned status: {status}")
- sys.exit(1)
- def check_workdir_state():
- if git("rev-parse", "--show-prefix", pipe=True) != "":
- print("Must be in root of git repo!")
- sys.exit(1)
- if git("branch", "--show-current", pipe=True) == "":
- print("Must be on a branch!")
- sys.exit(1)
- if not is_workdir_clean():
- print("Workdir must be clean!")
- sys.exit(1)
- def subdir_split_helper(path, repo, branch, subdir, action, cached=None):
- check_workdir_state()
- prevbranch = git("branch", "--show-current", pipe=True)
- temp = "/".join(repo.split("/")[-2:] + [branch]).replace("/", "-")
- fetch = f"_fetch-{temp}"
- split = f"_split-{temp}-{subdir.replace('/', '-')}"
- git("fetch", "--no-tags", repo, f"{branch}:{fetch}")
- current = git("rev-parse", fetch, pipe=True)
- skip = False
- if cached:
- try:
- git("diff", "--quiet", cached, current, "--", subdir)
- skip = True
- except subprocess.CalledProcessError:
- pass
- if skip:
- print("No updates, skipping expensive subtree split.")
- return
- ok = True
- git("checkout", fetch)
- result, status = git("subtree", "split", "-P", subdir, "-b", split, tee=True)
- if "is not an ancestor of commit" in result:
- print("Resetting split branch...")
- git("branch", "-D", split)
- git("subtree", "split", "-P", subdir, "-b", split)
- if "fatal: " in result:
- ok = False
- git("checkout", prevbranch)
- if ok:
- prevhead = git("rev-parse", "HEAD", pipe=True)
- result, status = git(
- "subtree",
- action,
- "-P",
- path,
- split,
- "-m",
- f"{action.title()} {path} from {repo}",
- tee=True,
- )
- check_merge_result(path, repo, result, status)
- if git("rev-parse", "HEAD", pipe=True) != prevhead:
- # Only return new upstream hash if subtree was updated
- # Not if merge was aborted or nothing to update
- return current
|