-
Notifications
You must be signed in to change notification settings - Fork 0
/
switch-api-branch
executable file
·212 lines (172 loc) · 7.19 KB
/
switch-api-branch
1
2
3
4
5
6
7
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
43
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#!/usr/bin/env python3
"""
First, you'll need the following repos checked out side by side:
src (name doesn't matter)
|-- api https://github.com/temporalio/api/
|-- cli https://github.com/temporalio/cli/
|-- api-go https://github.com/temporalio/api-go/
|-- sdk-go https://github.com/temporalio/sdk-go/
\-- temporal https://github.com/temporalio/temporal/
You'll also need to run this if you haven't already, to allow local submodules:
git config --global protocol.file.allow always
Next, pick a branch name. For example, we'll use "fig".
Go into src or any of the repos.
Run "switch-api-branch fig". This does the following:
- In api:
- switch to branch fig, creating it if necessary
- make proto to check it's good
- In api-go:
- switch to branch fig, creating it if necessary
- set the url and branch of the proto/api submodule to point to your local api repo
- update the proto/api submodule with the latest commit in api
- make proto
- In sdk-go and cli:
- switch to branch fig, creating it if necessary
- add or update a replace directive in go.mod for api-go (and sdk-go for cli)
- In temporal:
- switch to branch fig, creating it if necessary
- set the url and branch of the proto/api submodule to point to your local api repo
- update the proto/api submodule with the latest commit in api
- add or update replace directives in go.mod for api-go and sdk-go
- make proto
After all that happens, you should be good to develop on your branch across
repos. You can make changes in any of the four repos and run "switch-api-branch
fig" again, and everything will be updated and regenerated. (It's idempotent.)
To go back to normal, run "switch-api-branch main" (or "master").
switch-api-branch will create "NOT FOR MERGE: ..." commits with its changes, but
it will never push anything. You have to manually push and make PRs to api,
sdk-go, and temporal, as necessary. (PRs to api-go are usually not necessary.)
Suggested procedure: Let the PR to api land first, then api-go will be updated
automatically. Rebase your changes to temporal and/or sdk-go on top of master,
using an interactive rebase, and during that step, remove the "NOT FOR MERGE"
changes. Make sure they still work, then send PRs.
---
"""
# TODO: automate that rebasing procedure
# TODO: generate rpc client wrappers in server too
# TODO: run whatever codegen steps make sense in sdk-go
# TODO: do stuff for internal repos too?
import sys, os, argparse, subprocess
REPOS = ['api', 'api-go', 'sdk-go', 'cli', 'temporal']
ARGS = None
def run(*args, **kwargs):
print(f"+++ {' '.join(args)}")
return subprocess.run(args, check=True, **kwargs)
def git(*args, **kwargs):
return run("git", *args, **kwargs)
def git_out(*args, **kwargs):
return git(*args, capture_output=True, **kwargs).stdout.decode()
def repo_path(repo):
return os.path.join(ARGS.base, repo)
def cd_repo(repo):
print(f"+++ chdir {repo}")
os.chdir(repo_path(repo))
def find_base():
while os.getcwd() != '/':
if set(REPOS) <= set(os.listdir()):
return os.getcwd()
os.chdir('..')
raise Exception("can't find dir containing all repos")
def is_main(b):
return b in ('main', 'master')
def find_main():
out = git_out('branch', '--format', '%(refname:short) %(upstream:remotename)')
for line in out.splitlines():
try:
short, remote = line.split()
if is_main(short):
return short, remote
except ValueError:
pass
raise Exception(f"Can't find main/master branch in {os.getcwd()}")
def switch_or_create(branch, initial='FETCH_HEAD'):
main, remote = find_main()
if is_main(branch): # back to normal
branch = main
if (git_out('branch', '--list', branch) or
git_out('branch', '--list', '--remotes', f'{remote}/{branch}')):
git('switch', branch)
else: # branch does not exist, create it
git('fetch', remote, main)
git('switch', '-c', branch, initial)
# always update submodules when creating/switching branches
git('submodule', 'update', '--init', '--recursive')
def check_for_uncommitted_changes():
if git_out('status', '--short', '--untracked-files=no'):
raise Exception(f"You have uncommitted changes in {os.getcwd()}")
def git_ci(msg, *paths):
msg = "NOT FOR MERGE: " + msg
try:
if not paths: paths = ['-a']
git('commit', '-m', msg, *paths)
except subprocess.CalledProcessError:
# ignore "nothing to commit". TODO: check the details of the error
pass
def set_submodule(sub, path, branch):
# Note: path must be an absolute path here! Otherwise git will do something
# very unexpected.
git('submodule', 'set-url', sub, path)
git('submodule', 'set-branch', '--branch', branch, sub)
git_ci("setting submodule", '.gitmodules')
def update_submodules(*subs):
git('submodule', 'update', '--init', '--recursive', '--remote', *subs)
git_ci("updating submodule", *subs)
def go_mod_replace(pairs):
for pkg, path in pairs:
run('go', 'mod', 'edit', f'-replace={pkg}={path}')
run('go', 'mod', 'tidy')
git_ci("updating go.mod", 'go.mod', 'go.sum')
def make_proto(*commit_paths):
run('make', 'proto')
if commit_paths:
# use explicit paths to catch any potential new files, but try to avoid
# deliberate untracked files that might be lying around
git_ci("make proto", *commit_paths)
def switch():
resetting = is_main(ARGS.branch)
cd_repo('api')
check_for_uncommitted_changes()
switch_or_create(ARGS.branch)
if not resetting:
make_proto()
cd_repo('api-go')
check_for_uncommitted_changes()
switch_or_create(ARGS.branch)
if not resetting:
set_submodule('proto/api', repo_path('api'), ARGS.branch)
update_submodules('proto/api')
make_proto('.') # generated files are at root of repo
cd_repo('sdk-go')
check_for_uncommitted_changes()
switch_or_create(ARGS.branch)
if not resetting:
go_mod_replace([('go.temporal.io/api', repo_path('api-go'))])
cd_repo('temporal')
check_for_uncommitted_changes()
switch_or_create(ARGS.branch)
if not resetting:
set_submodule('proto/api', repo_path('api'), ARGS.branch)
update_submodules('proto/api')
go_mod_replace([('go.temporal.io/api', repo_path('api-go')),
('go.temporal.io/sdk', repo_path('sdk-go'))])
make_proto('api') # generated files are all contained in api/
cd_repo('cli')
check_for_uncommitted_changes()
switch_or_create(ARGS.branch)
if not resetting:
go_mod_replace([('go.temporal.io/api', repo_path('api-go')),
('go.temporal.io/sdk', repo_path('sdk-go')),
('go.temporal.io/server', repo_path('temporal')),
])
print(f"ready to develop on branch {ARGS.branch}")
def main():
p = argparse.ArgumentParser(usage=__doc__)
p.add_argument('branch', type=str, help="branch to switch to")
p.add_argument('--base', type=str, help="parent dir of all repos")
global ARGS
ARGS = p.parse_args()
if ARGS.base is None:
ARGS.base = find_base()
switch()
if __name__ == '__main__':
main()