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 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 76 | def git_ahead_behind(git_dir, branch, branch_upstream): |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 77 | ''' Count revisions ahead/behind of the remote branch. |
| 78 | returns: (ahead, behind) (e.g. (0, 5)) ''' |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 79 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 80 | # Missing remote branch |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 81 | if not branch_upstream: |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 82 | return (0, 0) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 83 | |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 84 | behind = git_output(git_dir, 'rev-list', '--count', '%s..%s' % (safe_branch_name(branch), branch_upstream)) |
| 85 | ahead = git_output(git_dir, 'rev-list', '--count', '%s..%s' % (branch_upstream, safe_branch_name(branch))) |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 86 | return (int(ahead.rstrip()), int(behind.rstrip())) |
| 87 | |
| 88 | |
| 89 | def git_branches(git_dir, obj='refs/heads'): |
| 90 | ret = git_output(git_dir, 'for-each-ref', obj, '--format', '%(refname:short)') |
| 91 | return ret.splitlines() |
| 92 | |
| 93 | |
| 94 | def git_branch_current(git_dir): |
| 95 | ret = git_output(git_dir, 'rev-parse', '--abbrev-ref', 'HEAD').rstrip() |
| 96 | if ret == 'HEAD': |
| 97 | return None |
| 98 | return ret |
| 99 | |
| 100 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 101 | def git_branch_upstream(git_dir, branch_name='HEAD'): |
| 102 | '''Return an upstream branch name, or an None if there is none.''' |
| 103 | try: |
| 104 | return git_output(git_dir, 'rev-parse', '--abbrev-ref', '%s@{u}' % branch_name).rstrip() |
| 105 | except subprocess.CalledProcessError: |
| 106 | return None |
| 107 | |
| 108 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 109 | def git_has_modifications(git_dir): |
Oliver Smith | 2a30b52 | 2018-11-16 16:47:57 +0100 | [diff] [blame] | 110 | return not git_bool(git_dir, 'diff', '--quiet', 'HEAD') |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 111 | |
| 112 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 113 | def git_can_fast_forward(git_dir, branch, branch_upstream): |
| 114 | return git_bool(git_dir, 'merge-base', '--is-ancestor', branch, branch_upstream) |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 115 | |
| 116 | |
| 117 | def format_branch_ahead_behind(branch, ahead, behind): |
| 118 | ''' branch: string like "master" |
| 119 | ahead, behind: integers like 5, 3 |
| 120 | returns: string like "master", "master[+5]", "master[-3]", "master[+5|-3]" ''' |
| 121 | # Just the branch |
| 122 | if not ahead and not behind: |
| 123 | return branch |
| 124 | |
| 125 | # Suffix with ahead/behind |
| 126 | ret = branch + '[' |
| 127 | if ahead: |
| 128 | ret += '+' + str(ahead) |
| 129 | if behind: |
| 130 | ret += '|' |
| 131 | if behind: |
| 132 | ret += '-' + str(behind) |
| 133 | ret += ']' |
| 134 | return ret |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 135 | |
| 136 | |
| 137 | def git_branch_summary(git_dir): |
| 138 | '''return a list of strings: [git_dir, branch-info0, branch-info1,...] |
| 139 | infos are are arbitrary strings like "master[-1]"''' |
| 140 | |
| 141 | interesting_branch_names = ('master',) |
| 142 | |
| 143 | strs = [git_dir, ] |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 144 | if git_has_modifications(git_dir): |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 145 | strs.append('MODS') |
| 146 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 147 | branch_current = git_branch_current(git_dir) |
| 148 | for branch in git_branches(git_dir): |
| 149 | is_current = (branch == branch_current) |
| 150 | if not is_current and branch not in interesting_branch_names: |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 151 | continue |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 152 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 153 | ahead, behind = git_ahead_behind(git_dir, branch, |
| 154 | git_branch_upstream(git_dir, branch)) |
| 155 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 156 | if not ahead and not behind and not is_current: |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 157 | # skip branches that are "not interesting" |
| 158 | continue |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 159 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 160 | # Branch with ahead/behind upstream info ("master[+1|-5]") |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 161 | strs.append(format_branch_ahead_behind(branch, ahead, behind)) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 162 | return strs |
| 163 | |
| 164 | |
| 165 | def format_summaries(summaries, sep0=' ', sep1=' '): |
| 166 | first_col = max([len(row[0]) for row in summaries]) |
| 167 | first_col_fmt = '%' + str(first_col) + 's' |
| 168 | |
| 169 | lines = [] |
| 170 | for row in summaries: |
| 171 | lines.append('%s%s%s' % (first_col_fmt % |
| 172 | row[0], sep0, sep1.join(row[1:]))) |
| 173 | |
| 174 | return '\n'.join(lines) |
| 175 | |
| 176 | |
| 177 | def git_dirs(): |
| 178 | dirs = [] |
| 179 | for sub in os.listdir(): |
| 180 | git_path = os.path.join(sub, '.git') |
| 181 | if not os.path.isdir(git_path): |
| 182 | continue |
| 183 | dirs.append(sub) |
| 184 | |
| 185 | if not dirs: |
| 186 | error('No subdirectories found that are git clones') |
| 187 | |
| 188 | return list(sorted(dirs)) |
| 189 | |
| 190 | |
| 191 | def print_status(): |
| 192 | infos = [git_branch_summary(git_dir) for git_dir in git_dirs()] |
| 193 | print(format_summaries(infos)) |
| 194 | |
| 195 | |
| 196 | def cmd_do(argv): |
| 197 | for git_dir in git_dirs(): |
| 198 | git(git_dir, *argv, may_fail=True, section_marker=True) |
| 199 | |
| 200 | |
| 201 | def cmd_sh(cmd): |
| 202 | if not cmd: |
| 203 | error('which command do you want to run?') |
| 204 | for git_dir in git_dirs(): |
| 205 | print('\n===== %s =====' % git_dir) |
| 206 | print('+ %s' % cmd_to_str(cmd)) |
| 207 | sys.stdout.flush() |
| 208 | subprocess.call(cmd, cwd=git_dir) |
| 209 | sys.stdout.flush() |
| 210 | sys.stderr.flush() |
| 211 | |
| 212 | |
| 213 | class SkipThisRepo(Exception): |
| 214 | pass |
| 215 | |
| 216 | |
| 217 | def ask(git_dir, *question, valid_answers=('*',)): |
| 218 | while True: |
| 219 | print('\n' + '\n '.join(question)) |
| 220 | print(' ' + '\n '.join(( |
| 221 | 's skip this repo', |
| 222 | 't show in tig', |
| 223 | 'g show in gitk', |
| 224 | ))) |
| 225 | |
| 226 | answer = sys.stdin.readline().strip() |
| 227 | if answer == 's': |
| 228 | raise SkipThisRepo() |
| 229 | if answer == 't': |
| 230 | subprocess.call(('tig', '--all'), cwd=git_dir) |
| 231 | continue |
| 232 | if answer == 'g': |
| 233 | subprocess.call(('gitk', '--all'), cwd=git_dir) |
| 234 | continue |
| 235 | |
| 236 | for v in valid_answers: |
| 237 | if v == answer: |
| 238 | return answer |
| 239 | if v == '*': |
| 240 | return answer |
| 241 | if v == '+' and len(answer): |
| 242 | return answer |
| 243 | |
| 244 | |
Neels Hofmeyr | ae79f4b | 2018-11-23 04:09:10 +0100 | [diff] [blame] | 245 | def ask_reset_hard_or_push_f(git_dir, orig_branch, upstream_branch): |
Neels Hofmeyr | dff944b | 2018-11-12 23:27:49 +0100 | [diff] [blame] | 246 | do_reset = ask(git_dir, 'Diverged.', |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 247 | '<empty> skip', |
| 248 | 'RH git reset --hard %s' % upstream_branch, |
| 249 | 'PF `push -f` to overwrite upstream', |
| 250 | valid_answers=('', 'RH', 'PF')) |
Neels Hofmeyr | dff944b | 2018-11-12 23:27:49 +0100 | [diff] [blame] | 251 | |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 252 | if do_reset == 'RH': |
Neels Hofmeyr | dff944b | 2018-11-12 23:27:49 +0100 | [diff] [blame] | 253 | git(git_dir, 'reset', '--hard', upstream_branch) |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 254 | elif do_reset == 'PF': |
Neels Hofmeyr | 20d95d0 | 2018-11-12 23:25:57 +0100 | [diff] [blame] | 255 | git(git_dir, 'push', '-f') |
Neels Hofmeyr | dff944b | 2018-11-12 23:27:49 +0100 | [diff] [blame] | 256 | |
| 257 | |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 258 | def rebase(git_dir): |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 259 | orig_branch = git_branch_current(git_dir) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 260 | if orig_branch is None: |
| 261 | print('Not on a branch: %s' % git_dir) |
| 262 | raise SkipThisRepo() |
| 263 | |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 264 | upstream_branch = git_branch_upstream(git_dir, orig_branch) |
| 265 | |
Neels Hofmeyr | 94e0aec | 2019-03-15 15:34:30 +0100 | [diff] [blame] | 266 | print('Checking for rebase of %r onto %r' % (orig_branch, upstream_branch)) |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 267 | |
| 268 | if git_has_modifications(git_dir): |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 269 | do_commit = ask(git_dir, 'Local mods.', |
| 270 | 'c commit to this branch', |
| 271 | '<name> commit to new branch', |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 272 | '<empty> skip this repo') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 273 | |
| 274 | if not do_commit: |
| 275 | raise SkipThisRepo() |
| 276 | |
| 277 | if do_commit == 'c': |
| 278 | git(git_dir, 'commit', '-am', 'wip', may_fail=True) |
| 279 | else: |
| 280 | git(git_dir, 'checkout', '-b', do_commit) |
| 281 | git(git_dir, 'commit', '-am', 'wip', may_fail=True) |
| 282 | git(git_dir, 'checkout', orig_branch) |
| 283 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 284 | if git_has_modifications(git_dir): |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 285 | error('There still are local modifications') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 286 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 287 | # Missing upstream branch |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 288 | if not upstream_branch: |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 289 | do_set_upstream = ask(git_dir, 'there is no upstream branch for %r' % orig_branch, |
| 290 | '<empty> skip', |
| 291 | 'p create upstream branch (git push --set-upstream orgin %s)' % orig_branch, |
| 292 | 'm checkout master', |
| 293 | valid_answers=('', 'p', 'm')) |
| 294 | |
| 295 | if do_set_upstream == 'p': |
| 296 | git(git_dir, 'push', '--set-upstream', 'origin', orig_branch); |
| 297 | upstream_branch = git_branch_upstream(git_dir, orig_branch) |
| 298 | if not upstream_branch: |
| 299 | error('There still is no upstream branch') |
| 300 | elif do_set_upstream == 'm': |
| 301 | git(git_dir, 'checkout', 'master') |
| 302 | return orig_branch |
| 303 | else: |
| 304 | print('skipping branch, because there is no upstream: %r' % orig_branch) |
| 305 | return orig_branch |
| 306 | |
| 307 | ahead, behind = git_ahead_behind(git_dir, orig_branch, upstream_branch) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 308 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 309 | # Diverged |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 310 | if ahead and behind: |
Neels Hofmeyr | ae79f4b | 2018-11-23 04:09:10 +0100 | [diff] [blame] | 311 | ask_reset_hard_or_push_f(git_dir, orig_branch, upstream_branch) |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 312 | |
| 313 | # Behind |
| 314 | elif behind: |
Neels Hofmeyr | 68d8f34 | 2018-11-12 22:44:08 +0100 | [diff] [blame] | 315 | if git_can_fast_forward(git_dir, orig_branch, upstream_branch): |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 316 | print('fast-forwarding...') |
| 317 | git(git_dir, 'merge') |
| 318 | else: |
| 319 | do_merge = ask(git_dir, 'Behind. git merge?', |
| 320 | "<empty> don't merge", |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 321 | 'm git merge', |
| 322 | valid_answers=('', 'm') |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 323 | ) |
| 324 | |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 325 | if do_merge == 'm': |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 326 | git(git_dir, 'merge') |
| 327 | |
Oliver Smith | b93f504 | 2018-11-09 10:34:36 +0100 | [diff] [blame] | 328 | # Ahead |
| 329 | elif ahead: |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 330 | do_commit = ask(git_dir, 'Ahead. commit to new branch?', |
| 331 | '<empty> no', |
| 332 | '<name> create new branch', |
| 333 | ) |
| 334 | if do_commit: |
| 335 | git(git_dir, 'checkout', '-b', do_commit) |
| 336 | git(git_dir, 'commit', '-am', 'wip', may_fail=True) |
| 337 | git(git_dir, 'checkout', orig_branch) |
| 338 | |
Neels Hofmeyr | ae79f4b | 2018-11-23 04:09:10 +0100 | [diff] [blame] | 339 | ask_reset_hard_or_push_f(git_dir, orig_branch, upstream_branch) |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 340 | |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 341 | if git_has_modifications(git_dir): |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 342 | error('There are local modifications') |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 343 | |
| 344 | # Rebase onto origin/master? Only when this isn't already the master branch |
| 345 | if upstream_branch != 'origin/master': |
| 346 | ahead, behind = git_ahead_behind(git_dir, orig_branch, 'origin/master') |
| 347 | |
| 348 | if ahead and behind: |
| 349 | do_rebase = ask(git_dir, '%r diverged from master. git rebase -i origin/master?' % orig_branch, |
| 350 | "<empty> don't rebase", |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 351 | 'r rebase onto origin/master', |
| 352 | valid_answers=('', 'r')) |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 353 | |
Neels Hofmeyr | bc2caa3 | 2020-08-26 15:24:13 +0200 | [diff] [blame^] | 354 | if do_rebase == 'r': |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 355 | git(git_dir, 'rebase', '-i', 'origin/master') |
| 356 | # On conflicts, we'll exit with error implicitly |
| 357 | |
Neels Hofmeyr | 7f46be3 | 2019-03-29 15:42:41 +0100 | [diff] [blame] | 358 | if upstream_branch is not None: |
| 359 | do_push = ask(git_dir, 'git push -f to overwrite %r?' % upstream_branch, |
| 360 | "<empty> don't overwrite upstream", |
| 361 | 'P `push -f` to overwrite upstream (P in caps!)', |
| 362 | valid_answers=('', 'P')) |
| 363 | if do_push == 'P': |
| 364 | git(git_dir, 'push', '-f') |
Neels Hofmeyr | efa34ac | 2019-03-15 15:34:45 +0100 | [diff] [blame] | 365 | |
Neels Hofmeyr | b459b6c | 2018-10-31 21:35:36 +0100 | [diff] [blame] | 366 | return orig_branch |
| 367 | |
| 368 | |
| 369 | def cmd_rebase(): |
| 370 | skipped = [] |
| 371 | for git_dir in git_dirs(): |
| 372 | try: |
| 373 | print('\n\n===== %s =====' % git_dir) |
| 374 | sys.stdout.flush() |
| 375 | |
| 376 | branch = rebase(git_dir) |
| 377 | if branch != 'master': |
| 378 | git(git_dir, 'checkout', 'master') |
| 379 | rebase(git_dir) |
| 380 | git(git_dir, 'checkout', branch) |
| 381 | |
| 382 | except SkipThisRepo: |
| 383 | print('\nSkipping %r' % git_dir) |
| 384 | skipped.append(git_dir) |
| 385 | |
| 386 | print('\n\n==========\nrebase done.\n') |
| 387 | print_status() |
| 388 | if skipped: |
| 389 | print('\nskipped: %s' % ' '.join(skipped)) |
| 390 | |
| 391 | |
| 392 | def parse_args(): |
| 393 | parser = argparse.ArgumentParser(description=doc) |
| 394 | sub = parser.add_subparsers(title='action', dest='action') |
| 395 | sub.required = True |
| 396 | |
| 397 | # status |
| 398 | sub.add_parser('status', aliases=['st', 's'], |
| 399 | help='show a branch summary and indicate modifications') |
| 400 | |
| 401 | # fetch |
| 402 | fetch = sub.add_parser('fetch', aliases=['f'], |
| 403 | help="run 'git fetch' in each clone (use before rebase)") |
| 404 | fetch.add_argument('remainder', nargs=argparse.REMAINDER, |
| 405 | help='additional arguments to be passed to git fetch') |
| 406 | |
| 407 | # rebase |
| 408 | sub.add_parser('rebase', aliases=['r', 're'], |
| 409 | help='interactively ff-merge master, rebase current branches') |
| 410 | |
| 411 | # sh |
| 412 | sh = sub.add_parser('sh', |
| 413 | help='run shell command in each clone (`gits sh echo hi`)') |
| 414 | sh.add_argument('remainder', nargs=argparse.REMAINDER, |
| 415 | help='command to run in each clone') |
| 416 | |
| 417 | # do |
| 418 | do = sub.add_parser('do', |
| 419 | help='run git command in each clone (`gits do clean -dxf`)') |
| 420 | do.add_argument('remainder', nargs=argparse.REMAINDER, |
| 421 | help='git command to run in each clone') |
| 422 | return parser.parse_args() |
| 423 | |
| 424 | |
| 425 | if __name__ == '__main__': |
| 426 | args = parse_args() |
| 427 | if args.action in ['status', 's', 'st']: |
| 428 | print_status() |
| 429 | elif args.action in ['fetch', 'f']: |
| 430 | cmd_do(['fetch'] + args.remainder) |
| 431 | elif args.action in ['rebase', 'r']: |
| 432 | cmd_rebase() |
| 433 | elif args.action == 'sh': |
| 434 | cmd_sh(args.remainder) |
| 435 | elif args.action == 'do': |
| 436 | cmd_do(args.remainder) |
| 437 | |
| 438 | # vim: shiftwidth=4 expandtab tabstop=4 |