common.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import subprocess
  2. import pathlib
  3. import time
  4. import sys
  5. REPO_ROOT = pathlib.Path(__file__).parent.parent
  6. def git(*args, pipe=False, tee=False):
  7. if tee:
  8. result = b""
  9. line = b""
  10. with subprocess.Popen(
  11. ["git", *args],
  12. stdout=subprocess.PIPE,
  13. stderr=subprocess.STDOUT,
  14. bufsize=0,
  15. ) as proc:
  16. cr = False
  17. while True:
  18. char = proc.stdout.read(1)
  19. if not char:
  20. result += line
  21. break
  22. sys.stdout.write(char.decode())
  23. # Simulate terminal line buffering, where \r resets to start of line overwriting what is already there
  24. # unless \n follows it, which is just going to next line keeping the content on previous one
  25. # Useful for terminal progress bars/numbers, no need to log all this data as separate lines
  26. if char == b"\r":
  27. cr = True
  28. continue
  29. elif cr:
  30. cr = False
  31. if char != b"\n":
  32. line = b""
  33. line += char
  34. if char == b"\n":
  35. result += line
  36. line = b""
  37. return result.decode().removesuffix("\n"), proc.returncode
  38. elif pipe:
  39. return subprocess.check_output(["git", *args], text=True).removesuffix("\n")
  40. else:
  41. return subprocess.check_call(["git", *args])
  42. def is_workdir_clean():
  43. try:
  44. git("diff", "--quiet")
  45. git("diff", "--cached", "--quiet")
  46. git("merge", "HEAD", pipe=True)
  47. return True
  48. except subprocess.CalledProcessError:
  49. return False
  50. def send_alert(title, message):
  51. try:
  52. subprocess.run(
  53. ["notify-send", "-a", "Git", "-i", "git", title, message],
  54. stdout=subprocess.PIPE,
  55. stderr=subprocess.PIPE,
  56. )
  57. except Exception:
  58. pass
  59. def check_merge_result(path, repo, result, status):
  60. if "Automatic merge failed; fix conflicts and then commit the result." in result:
  61. print(f"MERGE_MSG: Merge {path} from {repo}")
  62. send_alert("Subtree merge failed", "Resolve current index to continue")
  63. while True:
  64. input("Resolve current index then press Enter...")
  65. if is_workdir_clean():
  66. break
  67. time.sleep(1)
  68. elif (
  69. "Please commit your changes or stash them before you switch branches." in result
  70. ):
  71. sys.exit(1)
  72. elif "fatal: " in result:
  73. sys.exit(1)
  74. elif status != 0:
  75. print(f"Git returned status: {status}")
  76. sys.exit(1)
  77. def check_workdir_state():
  78. if git("rev-parse", "--show-prefix", pipe=True) != "":
  79. print("Must be in root of git repo!")
  80. sys.exit(1)
  81. if git("branch", "--show-current", pipe=True) == "":
  82. print("Must be on a branch!")
  83. sys.exit(1)
  84. if not is_workdir_clean():
  85. print("Workdir must be clean!")
  86. sys.exit(1)
  87. def subdir_split_helper(path, repo, branch, subdir, action, cached=None):
  88. check_workdir_state()
  89. prevbranch = git("branch", "--show-current", pipe=True)
  90. temp = "/".join(repo.split("/")[-2:] + [branch]).replace("/", "-")
  91. fetch = f"_fetch-{temp}"
  92. split = f"_split-{temp}-{subdir.replace('/', '-')}"
  93. git("fetch", "--no-tags", repo, f"{branch}:{fetch}")
  94. current = git("rev-parse", fetch, pipe=True)
  95. skip = False
  96. if cached:
  97. try:
  98. git("diff", "--quiet", cached, current, "--", subdir)
  99. skip = True
  100. except subprocess.CalledProcessError:
  101. pass
  102. if skip:
  103. print("No updates, skipping expensive subtree split.")
  104. return
  105. ok = True
  106. git("checkout", fetch)
  107. result, status = git("subtree", "split", "-P", subdir, "-b", split, tee=True)
  108. if "is not an ancestor of commit" in result:
  109. print("Resetting split branch...")
  110. git("branch", "-D", split)
  111. result, status = git("subtree", "split", "-P", subdir, "-b", split, tee=True)
  112. if "fatal: " in result:
  113. ok = False
  114. git("checkout", prevbranch)
  115. if not ok:
  116. print(f"Something went wrong with {path}/.gitsubtree, check above")
  117. send_alert("Subtree merge failed", "Something went wrong, check logs")
  118. sys.exit(1)
  119. else:
  120. prevhead = git("rev-parse", "HEAD", pipe=True)
  121. result, status = git(
  122. "subtree",
  123. action,
  124. "-P",
  125. path,
  126. split,
  127. "-m",
  128. f"{action.title()} {path} from {repo}",
  129. tee=True,
  130. )
  131. check_merge_result(path, repo, result, status)
  132. if git("rev-parse", "HEAD", pipe=True) != prevhead:
  133. # Only return new upstream hash if subtree was updated
  134. # Not if merge was aborted or nothing to update
  135. return current