Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # (C) 2018 by Neels Hofmeyr <neels@hofmeyr.de> |
| 4 | # All rights reserved. |
| 5 | # |
| 6 | # This program is free software: you can redistribute it and/or modify |
| 7 | # it under the terms of the GNU General Public License as published by |
| 8 | # the Free Software Foundation, either version 3 of the License, or |
| 9 | # (at your option) any later version. |
| 10 | # |
| 11 | # This program is distributed in the hope that it will be useful, |
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | # GNU General Public License for more details. |
| 15 | # |
| 16 | # You should have received a copy of the GNU General Public License |
| 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | |
| 19 | import sys |
| 20 | import subprocess |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 21 | import argparse |
| 22 | import os |
| 23 | import shlex |
| 24 | |
| 25 | doc = '''gits: conveniently manage several git subdirectories. |
| 26 | Instead of doing the 'cd foo; git status; cd ../bar; git status' dance, this |
| 27 | helps to save your time with: status, fetch, rebase, ... |
| 28 | ''' |
| 29 | |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 30 | |
| 31 | def error(*msgs): |
| 32 | sys.stderr.write(''.join(msgs)) |
| 33 | sys.stderr.write('\n') |
| 34 | exit(1) |
| 35 | |
| 36 | |
| 37 | def cmd_to_str(cmd): |
| 38 | return ' '.join(shlex.quote(c) for c in cmd) |
| 39 | |
| 40 | |
| 41 | def git(git_dir, *args, may_fail=False, section_marker=False, show_cmd=True): |
| 42 | sys.stdout.flush() |
| 43 | sys.stderr.flush() |
| 44 | |
| 45 | if section_marker: |
| 46 | print('\n===== %s =====' % git_dir) |
| 47 | sys.stdout.flush() |
| 48 | |
| 49 | cmd = ['git', '-C', git_dir] + list(args) |
| 50 | if show_cmd: |
| 51 | print('+ %s' % cmd_to_str(cmd)) |
| 52 | sys.stdout.flush() |
| 53 | |
| 54 | rc = subprocess.call(cmd) |
| 55 | if rc and not may_fail: |
| 56 | error('git returned error! command: git -C %r %s' % |
| 57 | (git_dir, ' '.join(repr(arg) for arg in args))) |
| 58 | |
| 59 | |
| 60 | def git_output(git_dir, *args): |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 61 | return subprocess.check_output(['git', '-C', git_dir, ] + list(args), stderr=subprocess.STDOUT).decode('utf-8') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 62 | |
| 63 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 64 | def git_bool(git_dir, *args): |
| 65 | try: |
| 66 | subprocess.check_output(['git', '-C', git_dir, ] + list(args)) |
| 67 | return True |
| 68 | except subprocess.CalledProcessError as e: |
| 69 | return False |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 70 | |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame] | 71 | def safe_branch_name(branch): |
| 72 | if '/' in branch: |
| 73 | return branch |
| 74 | return 'refs/heads/' + branch |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 75 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 76 | def git_branches(git_dir, obj='refs/heads'): |
| 77 | ret = git_output(git_dir, 'for-each-ref', obj, '--format', '%(refname:short)') |
| 78 | return ret.splitlines() |
| 79 | |
| 80 | |
| 81 | def git_branch_current(git_dir): |
| 82 | ret = git_output(git_dir, 'rev-parse', '--abbrev-ref', 'HEAD').rstrip() |
| 83 | if ret == 'HEAD': |
| 84 | return None |
| 85 | return ret |
| 86 | |
| 87 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 88 | def git_branch_upstream(git_dir, branch_name='HEAD'): |
| 89 | '''Return an upstream branch name, or an None if there is none.''' |
| 90 | try: |
| 91 | return git_output(git_dir, 'rev-parse', '--abbrev-ref', '%s@{u}' % branch_name).rstrip() |
| 92 | except subprocess.CalledProcessError: |
| 93 | return None |
| 94 | |
| 95 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 96 | def git_has_modifications(git_dir): |
Oliver Smith | 2a30b52 | 2018-11-16 16:47:57 +0100 | [diff] [blame] | 97 | return not git_bool(git_dir, 'diff', '--quiet', 'HEAD') |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 98 | |
| 99 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 100 | def git_can_fast_forward(git_dir, branch, branch_upstream): |
| 101 | return git_bool(git_dir, 'merge-base', '--is-ancestor', branch, branch_upstream) |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 102 | |
| 103 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 104 | class AheadBehind: |
| 105 | ''' Count revisions ahead/behind of the remote branch. |
| 106 | returns: (ahead, behind) (e.g. (0, 5)) ''' |
| 107 | def __init__(s, git_dir, local, remote): |
| 108 | s.git_dir = git_dir |
| 109 | s.local = local |
| 110 | s.remote = remote |
| 111 | s.can_ff = False |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 112 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 113 | if not remote: |
| 114 | s.ahead = 0 |
| 115 | s.behind = 0 |
| 116 | else: |
| 117 | behind_str = git_output(git_dir, 'rev-list', '--count', '%s..%s' % (safe_branch_name(local), remote)) |
| 118 | ahead_str = git_output(git_dir, 'rev-list', '--count', '%s..%s' % (remote, safe_branch_name(local))) |
| 119 | s.ahead = int(ahead_str.rstrip()) |
| 120 | s.behind = int(behind_str.rstrip()) |
| 121 | s.can_ff = s.behind and git_can_fast_forward(git_dir, local, remote) |
| 122 | |
| 123 | |
| 124 | |
| 125 | def is_diverged(s): |
| 126 | return s.ahead and s.behind |
| 127 | |
| 128 | def is_behind(s): |
| 129 | return (not s.ahead) and s.behind |
| 130 | |
| 131 | def is_ahead(s): |
| 132 | return s.ahead and not s.behind |
| 133 | |
| 134 | def is_sync(s): |
| 135 | return s.ahead == 0 and s.behind == 0 |
| 136 | |
| 137 | def ff(s): |
| 138 | print('fast-forwarding %s to %s...' % (s.local, s.remote)) |
| 139 | if git_branch_current(s.git_dir) != s.local: |
| 140 | git(s.git_dir, 'checkout', s.local) |
| 141 | git(s.git_dir, 'merge', s.remote) |
| 142 | |
| 143 | def __str__(s): |
| 144 | # Just the branch |
| 145 | if not s.ahead and not s.behind: |
| 146 | return s.local |
| 147 | |
| 148 | # Suffix with ahead/behind |
| 149 | ret = s.local + '[' |
| 150 | if s.ahead: |
| 151 | ret += '+' + str(s.ahead) |
| 152 | if s.behind: |
| 153 | ret += '|' |
| 154 | if s.behind: |
| 155 | ret += '-' + str(s.behind) |
| 156 | ret += ']' |
| 157 | return ret |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 158 | |
| 159 | |
| 160 | def git_branch_summary(git_dir): |
| 161 | '''return a list of strings: [git_dir, branch-info0, branch-info1,...] |
| 162 | infos are are arbitrary strings like "master[-1]"''' |
| 163 | |
| 164 | interesting_branch_names = ('master',) |
| 165 | |
| 166 | strs = [git_dir, ] |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 167 | if git_has_modifications(git_dir): |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 168 | strs.append('MODS') |
| 169 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 170 | branch_current = git_branch_current(git_dir) |
| 171 | for branch in git_branches(git_dir): |
| 172 | is_current = (branch == branch_current) |
| 173 | if not is_current and branch not in interesting_branch_names: |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 174 | continue |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 175 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 176 | ab = AheadBehind(git_dir, branch, git_branch_upstream(git_dir, branch)) |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 177 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 178 | if not ab.ahead and not ab.behind and not is_current: |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 179 | # skip branches that are "not interesting" |
| 180 | continue |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 181 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 182 | # Branch with ahead/behind upstream info ("master[+1|-5]") |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 183 | strs.append(str(ab)) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 184 | return strs |
| 185 | |
| 186 | |
| 187 | def format_summaries(summaries, sep0=' ', sep1=' '): |
| 188 | first_col = max([len(row[0]) for row in summaries]) |
| 189 | first_col_fmt = '%' + str(first_col) + 's' |
| 190 | |
| 191 | lines = [] |
| 192 | for row in summaries: |
| 193 | lines.append('%s%s%s' % (first_col_fmt % |
| 194 | row[0], sep0, sep1.join(row[1:]))) |
| 195 | |
| 196 | return '\n'.join(lines) |
| 197 | |
| 198 | |
| 199 | def git_dirs(): |
| 200 | dirs = [] |
| 201 | for sub in os.listdir(): |
| 202 | git_path = os.path.join(sub, '.git') |
| 203 | if not os.path.isdir(git_path): |
| 204 | continue |
| 205 | dirs.append(sub) |
| 206 | |
| 207 | if not dirs: |
| 208 | error('No subdirectories found that are git clones') |
| 209 | |
| 210 | return list(sorted(dirs)) |
| 211 | |
| 212 | |
| 213 | def print_status(): |
| 214 | infos = [git_branch_summary(git_dir) for git_dir in git_dirs()] |
| 215 | print(format_summaries(infos)) |
| 216 | |
| 217 | |
| 218 | def cmd_do(argv): |
| 219 | for git_dir in git_dirs(): |
| 220 | git(git_dir, *argv, may_fail=True, section_marker=True) |
| 221 | |
| 222 | |
| 223 | def cmd_sh(cmd): |
| 224 | if not cmd: |
| 225 | error('which command do you want to run?') |
| 226 | for git_dir in git_dirs(): |
| 227 | print('\n===== %s =====' % git_dir) |
| 228 | print('+ %s' % cmd_to_str(cmd)) |
| 229 | sys.stdout.flush() |
| 230 | subprocess.call(cmd, cwd=git_dir) |
| 231 | sys.stdout.flush() |
| 232 | sys.stderr.flush() |
| 233 | |
| 234 | |
| 235 | class SkipThisRepo(Exception): |
| 236 | pass |
| 237 | |
| 238 | |
| 239 | def ask(git_dir, *question, valid_answers=('*',)): |
| 240 | while True: |
| 241 | print('\n' + '\n '.join(question)) |
| 242 | print(' ' + '\n '.join(( |
| 243 | 's skip this repo', |
| 244 | 't show in tig', |
| 245 | 'g show in gitk', |
| 246 | ))) |
| 247 | |
| 248 | answer = sys.stdin.readline().strip() |
| 249 | if answer == 's': |
| 250 | raise SkipThisRepo() |
| 251 | if answer == 't': |
| 252 | subprocess.call(('tig', '--all'), cwd=git_dir) |
| 253 | continue |
| 254 | if answer == 'g': |
| 255 | subprocess.call(('gitk', '--all'), cwd=git_dir) |
| 256 | continue |
| 257 | |
| 258 | for v in valid_answers: |
| 259 | if v == answer: |
| 260 | return answer |
| 261 | if v == '*': |
| 262 | return answer |
| 263 | if v == '+' and len(answer): |
| 264 | return answer |
| 265 | |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 266 | def rebase(git_dir): |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 267 | orig_branch = git_branch_current(git_dir) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 268 | if orig_branch is None: |
| 269 | print('Not on a branch: %s' % git_dir) |
| 270 | raise SkipThisRepo() |
| 271 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 272 | upstream_branch = git_branch_upstream(git_dir, orig_branch) |
| 273 | |
Neels Hofmeyr | 94e0aec | 2019-03-15 15:34:30 +0100 | [diff] [blame] | 274 | print('Checking for rebase of %r onto %r' % (orig_branch, upstream_branch)) |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 275 | |
| 276 | if git_has_modifications(git_dir): |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 277 | do_commit = ask(git_dir, 'Local mods.', |
| 278 | 'c commit to this branch', |
| 279 | '<name> commit to new branch', |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame] | 280 | '<empty> skip this repo') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 281 | |
| 282 | if not do_commit: |
| 283 | raise SkipThisRepo() |
| 284 | |
| 285 | if do_commit == 'c': |
| 286 | git(git_dir, 'commit', '-am', 'wip', may_fail=True) |
| 287 | else: |
| 288 | git(git_dir, 'checkout', '-b', do_commit) |
| 289 | git(git_dir, 'commit', '-am', 'wip', may_fail=True) |
| 290 | git(git_dir, 'checkout', orig_branch) |
| 291 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 292 | if git_has_modifications(git_dir): |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame] | 293 | error('There still are local modifications') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 294 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 295 | # Missing upstream branch |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 296 | if not upstream_branch: |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame] | 297 | do_set_upstream = ask(git_dir, 'there is no upstream branch for %r' % orig_branch, |
| 298 | '<empty> skip', |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 299 | 'P create upstream branch (git push --set-upstream orgin %s)' % orig_branch, |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame] | 300 | 'm checkout master', |
| 301 | valid_answers=('', 'p', 'm')) |
| 302 | |
| 303 | if do_set_upstream == 'p': |
| 304 | git(git_dir, 'push', '--set-upstream', 'origin', orig_branch); |
| 305 | upstream_branch = git_branch_upstream(git_dir, orig_branch) |
| 306 | if not upstream_branch: |
| 307 | error('There still is no upstream branch') |
| 308 | elif do_set_upstream == 'm': |
| 309 | git(git_dir, 'checkout', 'master') |
| 310 | return orig_branch |
| 311 | else: |
| 312 | print('skipping branch, because there is no upstream: %r' % orig_branch) |
| 313 | return orig_branch |
| 314 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 315 | while True: |
| 316 | # bu: branch-to-upstream |
| 317 | # bm: branch-to-master |
| 318 | # um: upstream-to-master |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 319 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 320 | upstream_branch = git_branch_upstream(git_dir, orig_branch) |
| 321 | um = AheadBehind(git_dir, upstream_branch, 'origin/master') |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 322 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 323 | bm = AheadBehind(git_dir, orig_branch, 'origin/master') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 324 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 325 | if bm.can_ff: |
| 326 | bm.ff() |
| 327 | continue |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 328 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 329 | bu = AheadBehind(git_dir, orig_branch, upstream_branch) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 330 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 331 | if bu.can_ff: |
| 332 | bu.ff() |
| 333 | continue |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 334 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 335 | if not bu.is_sync(): |
| 336 | print(str(bu)) |
| 337 | if not bm.is_sync(): |
| 338 | print('to master: ' + str(bm)) |
| 339 | if not um.is_sync(): |
| 340 | print('upstream to master: ' + str(um)) |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 341 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 342 | options = ['----- %s' % git_dir, |
| 343 | '<empty> skip'] |
| 344 | valid_answers = [''] |
| 345 | all_good = True |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 346 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 347 | if um.is_diverged(): |
| 348 | all_good = False |
| 349 | if bu.is_diverged(): |
| 350 | options.append('rum rebase onto upstream, then onto master') |
| 351 | valid_answers.append('rum') |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 352 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 353 | #if bm.is_diverged(): |
| 354 | options.append('rm rebase onto master: git rebase -i origin/master') |
| 355 | valid_answers.append('rm') |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 356 | |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 357 | if bu.is_diverged(): |
| 358 | all_good = False |
| 359 | options.append('ru rebase onto upstream: git rebase -i %s' % upstream_branch) |
| 360 | valid_answers.append('ru') |
| 361 | |
| 362 | options.append('RU reset to upstream: git reset --hard %s' % upstream_branch) |
| 363 | valid_answers.append('RU') |
| 364 | |
| 365 | if bu.is_diverged() or bu.is_ahead(): |
| 366 | all_good = False |
| 367 | options.append('P push to overwrite upstream: git push -f') |
| 368 | valid_answers.append('P') |
| 369 | |
| 370 | if orig_branch == 'master' and (bm.is_ahead() or bm.is_diverged()): |
| 371 | all_good = False |
| 372 | options.append('<name> create new branch') |
| 373 | valid_answers.append('+') |
| 374 | |
| 375 | if all_good: |
| 376 | break |
| 377 | |
| 378 | do = ask(git_dir, *options, valid_answers=valid_answers) |
| 379 | |
| 380 | if not do: |
| 381 | break |
| 382 | |
| 383 | if do == 'rum' or do == 'ru': |
| 384 | git(git_dir, 'rebase', '-i', upstream_branch) |
| 385 | |
| 386 | if do == 'rum' or do == 'rm': |
| 387 | git(git_dir, 'rebase', '-i', 'origin/master') |
| 388 | |
| 389 | if do == 'RU': |
| 390 | git(git_dir, 'reset', '--hard', upstream_branch) |
| 391 | |
| 392 | if do == 'P': |
| 393 | git(git_dir, 'push', '-f') |
| 394 | |
| 395 | if do not in valid_answers: |
| 396 | new_branch = do |
| 397 | # create new branch |
| 398 | print('''git(git_dir, 'checkout', '-b', new_branch)''') |
| 399 | #orig_branch = new_branch |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 400 | |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 401 | return orig_branch |
| 402 | |
| 403 | |
| 404 | def cmd_rebase(): |
| 405 | skipped = [] |
| 406 | for git_dir in git_dirs(): |
| 407 | try: |
| 408 | print('\n\n===== %s =====' % git_dir) |
| 409 | sys.stdout.flush() |
| 410 | |
| 411 | branch = rebase(git_dir) |
| 412 | if branch != 'master': |
Neels Hofmeyr | 67e53d9 | 2020-09-08 01:37:05 +0200 | [diff] [blame^] | 413 | mm = AheadBehind(git_dir, 'master', 'origin/master') |
| 414 | if not mm.is_sync(): |
| 415 | git(git_dir, 'checkout', 'master') |
| 416 | rebase(git_dir) |
| 417 | git(git_dir, 'checkout', branch) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 418 | |
| 419 | except SkipThisRepo: |
| 420 | print('\nSkipping %r' % git_dir) |
| 421 | skipped.append(git_dir) |
| 422 | |
| 423 | print('\n\n==========\nrebase done.\n') |
| 424 | print_status() |
| 425 | if skipped: |
| 426 | print('\nskipped: %s' % ' '.join(skipped)) |
| 427 | |
| 428 | |
| 429 | def parse_args(): |
| 430 | parser = argparse.ArgumentParser(description=doc) |
| 431 | sub = parser.add_subparsers(title='action', dest='action') |
| 432 | sub.required = True |
| 433 | |
| 434 | # status |
| 435 | sub.add_parser('status', aliases=['st', 's'], |
| 436 | help='show a branch summary and indicate modifications') |
| 437 | |
| 438 | # fetch |
| 439 | fetch = sub.add_parser('fetch', aliases=['f'], |
| 440 | help="run 'git fetch' in each clone (use before rebase)") |
| 441 | fetch.add_argument('remainder', nargs=argparse.REMAINDER, |
| 442 | help='additional arguments to be passed to git fetch') |
| 443 | |
| 444 | # rebase |
| 445 | sub.add_parser('rebase', aliases=['r', 're'], |
| 446 | help='interactively ff-merge master, rebase current branches') |
| 447 | |
| 448 | # sh |
| 449 | sh = sub.add_parser('sh', |
| 450 | help='run shell command in each clone (`gits sh echo hi`)') |
| 451 | sh.add_argument('remainder', nargs=argparse.REMAINDER, |
| 452 | help='command to run in each clone') |
| 453 | |
| 454 | # do |
| 455 | do = sub.add_parser('do', |
| 456 | help='run git command in each clone (`gits do clean -dxf`)') |
| 457 | do.add_argument('remainder', nargs=argparse.REMAINDER, |
| 458 | help='git command to run in each clone') |
| 459 | return parser.parse_args() |
| 460 | |
| 461 | |
| 462 | if __name__ == '__main__': |
| 463 | args = parse_args() |
| 464 | if args.action in ['status', 's', 'st']: |
| 465 | print_status() |
| 466 | elif args.action in ['fetch', 'f']: |
| 467 | cmd_do(['fetch'] + args.remainder) |
| 468 | elif args.action in ['rebase', 'r']: |
| 469 | cmd_rebase() |
| 470 | elif args.action == 'sh': |
| 471 | cmd_sh(args.remainder) |
| 472 | elif args.action == 'do': |
| 473 | cmd_do(args.remainder) |
| 474 | |
| 475 | # vim: shiftwidth=4 expandtab tabstop=4 |