3 from pathlib import Path as P
4 from urllib.parse import urlparse
5 from contextlib import contextmanager
18 URL = "https://gitlab.freedesktop.org/"
19 PARSER = argparse.ArgumentParser(
20 description="`Rebase` a branch from an old GStreamer module onto the monorepo"
22 PARSER.add_argument("repo", help="The repo with the old module to use.")
23 PARSER.add_argument("branch", help="The branch to rebase.")
25 log_depth = [] # type: T.List[str]
30 log_depth.append(name)
37 return f"\033[1m{text}\033[0m"
40 return f"\033[1;32m{text}\033[0m"
43 return f"\033[1;31m{text}\033[0m"
45 def yellow(text: str):
46 return f"\033[1;33m{text}\033[0m"
48 def fprint(msg, nested=True):
50 prepend = log_depth[-1] + ' | ' if nested else ''
54 print(prepend + msg, end="")
58 class GstCherryPicker:
65 self.git_rename_limit = None
67 def check_clean(self):
69 out = self.git("status", "--porcelain")
71 fprint("\n" + red('Git repository is not clean:') + "\n```\n" + out + "\n```\n")
74 except Exception as e:
76 f"Git repository is not clean. Clean it up before running ({e})")
84 git_rename_limit = int(self.git("config", "merge.renameLimit"))
85 except subprocess.CalledProcessError:
87 if int(git_rename_limit) < 999999:
88 self.git_rename_limit = git_rename_limit
89 fprint("-> Setting git rename limit to 999999 so we can properly cherry-pick between repos")
90 self.git("config", "merge.renameLimit", "999999")
91 fprint(f"{green(' OK')}\n", nested=False)
96 if self.git_rename_limit is not None:
97 self.git("config", "merge.renameLimit", str(self.git_rename_limit))
100 repo = urlparse(self.repo)
102 repo_path = P(repo.path)
103 self.module = module = repo_path.stem
104 remote_name = f"{module}-{repo_path.parent.name}"
105 fprint('Adding remotes...')
106 self.git("remote", "add", remote_name, self.repo, can_fail=True)
107 self.git("remote", "add", module, f"{URL}gstreamer/{module}.git",
109 fprint(f"{green(' OK')}\n", nested=False)
111 fprint(f'Fetching {remote_name}...')
112 self.git("fetch", remote_name,
113 interaction_message=f"fetching {remote_name} with:\n"
114 f" `$ git fetch {remote_name}`")
115 fprint(f"{green(' OK')}\n", nested=False)
117 fprint(f'Fetching {module}...')
118 self.git("fetch", module,
119 interaction_message=f"fetching {module} with:\n"
120 f" `$ git fetch {module}`")
121 fprint(f"{green(' OK')}\n", nested=False)
123 prevbranch = self.git("rev-parse", "--abbrev-ref", "HEAD").strip()
124 tmpbranchname = f"{remote_name}_{self.branch}"
125 fprint(f'Checking out branch {remote_name}/{self.branch} as {tmpbranchname}\n')
127 self.git("checkout", f"{remote_name}/{self.branch}", "-b", tmpbranchname)
128 self.git("rebase", f"{module}/master",
129 interaction_message=f"Failed rebasing {remote_name}/{self.branch} on {module}/master with:\n"
130 f" `$ git rebase {module}/master`")
131 self.cherry_pick(tmpbranchname)
133 self.git("rebase", "--abort", can_fail=True)
134 self.git("checkout", prevbranch)
135 self.git("branch", "-D", tmpbranchname)
137 fprint(f"{green(' OK')}\n", nested=False)
139 def cherry_pick(self, branch):
140 shas = self.git('log', '--format=format:%H', f'{self.module}/master..').strip()
141 fprint(f'Resetting on origin/main')
142 self.git("reset", "--hard", "origin/main")
143 fprint(f"{green(' OK')}\n", nested=False)
145 for sha in reversed(shas.split()):
146 fprint(f' - Cherry picking: {bold(sha)}\n')
147 self.git("cherry-pick", sha,
148 interaction_message=f"cherry-picking {sha} onto {branch} with:\n "
149 f" `$ git cherry-pick {sha}`"
153 def git(self, *args, can_fail=False, interaction_message=None, call=False):
160 return subprocess.check_output(["git"] + list(args),
161 stdin=subprocess.DEVNULL,
162 stderr=subprocess.STDOUT).decode()
165 fprint(f"\n\n{bold(red('ERROR'))}: `git {' '.join(args)}` failed" + "\n", nested=False)
168 subprocess.call(["git"] + list(args))
170 except Exception as e:
171 if interaction_message:
172 output = getattr(e, "output", b"")
173 if output is not None:
174 out = output.decode()
179 f"Entering a shell to fix:\n\n"
180 f" {bold(interaction_message)}\n\n"
181 f"You should then exit with the following codes:\n\n"
182 f" - {bold('`exit 0`')}: once you have fixed the problem and we can keep moving the \n"
183 f" - {bold('`exit 1`')}: {bold('retry')}: once you have let the repo in a state where cherry-picking the commit should be to retried\n"
184 f" - {bold('`exit 3`')}: stop the script and abandon moving your MRs\n"
185 "\n```\n", nested=False)
188 shell = os.environ.get(
189 "COMSPEC", r"C:\WINDOWS\system32\cmd.exe")
191 shell = os.environ.get(
192 "SHELL", os.path.realpath("/bin/sh"))
193 subprocess.check_call(shell)
194 except subprocess.CalledProcessError as e:
195 if e.returncode == 1:
198 elif e.returncode == 3:
201 # Result of subshell does not really matter
204 return "User fixed it"
207 return "Failed but we do not care"
213 picker = GstCherryPicker()
214 PARSER.parse_args(namespace=picker)
218 if __name__ == '__main__':