aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2018-10-31 21:35:36 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2018-11-03 18:24:08 +0100
commit7d46f2ea30e171cf63bf5e7b94879cba16f40a15 (patch)
treeefced707edf71834e3616b903cb5cdf3de762b13
parent5c882f66e9291634cc4c1b341b81a0e6e2c12022 (diff)
replace src/* git scripts with a single src/gitsneels/gits
I keep re-using this functionality in completely unrelated realms, and decided to unify the oddly named scripts in a single 'gits' meta-repos tool, so I can just symlink this script into my ~/bin and use it everywhere. Change-Id: I579e7af26d76d5c5d83b2349695456bc7b54f5a2
-rw-r--r--src/README23
-rwxr-xr-xsrc/e15
-rwxr-xr-xsrc/g17
-rwxr-xr-xsrc/git_branch_summary.py68
-rwxr-xr-xsrc/gits362
-rwxr-xr-xsrc/s176
-rwxr-xr-xsrc/st10
7 files changed, 373 insertions, 298 deletions
diff --git a/src/README b/src/README
index a2fbe81..f066561 100644
--- a/src/README
+++ b/src/README
@@ -11,12 +11,11 @@ There are some handy scripts I use for my daily Osmocom development:
Pass a patch number seen on gerrit to fetch the latest patch set into
your git clone. See top comment in the script.
- ./g run a git command in each source tree
- ./e run an arbitrary shell command in each source tree
- ./st show a brief branch and local mods status for each source tree
- ./s walk through each source tree and use gitk as well as user interaction
- to quickly fast-forward / reset changes coming in from upstream. (This
- is potentially dangerous, but safe if you only hit enter every time.)
+ gits Conveniently manage several git clones:
+ - run a git or shell command in each source tree
+ - show a brief branch and local mods status for each source tree
+ - merge / rebase / fast-forward each source tree interactively
+ See ./gits help
Examples:
@@ -54,7 +53,7 @@ Switched to a new branch '3787_1'
-----------------------------------------------------------------------------
-./g fetch # run 'git fetch' in each clone = fetch all from upstream
+./gits fetch # run 'git fetch' in each clone = fetch all from upstream
===== libasn1c =====
remote: Counting objects: 29, done
@@ -90,7 +89,7 @@ From ssh://go/libosmocore
-----------------------------------------------------------------------------
-./st # any modifications / updates? (e.g. useful after './g fetch')
+./gits st # any modifications / updates? (e.g. useful after './g fetch')
# (checks only 'master' and the current checked-out branch)
libasn1c master
@@ -116,13 +115,13 @@ libosmo-netif master
-----------------------------------------------------------------------------
-./e rm .version # in each source tree, remove the local .version file
+./gits sh rm .version # in each source tree, remove the local .version file
-----------------------------------------------------------------------------
-./s # interactively try to fast-forward to upstream and/or save
- # local modifications.
- # If you just hit Enter all the time, nothing will be changed.
+./gits rebase # interactively try to fast-forward to upstream and/or save
+ # local modifications.
+ # If you just hit Enter all the time, nothing dangerous will happen.
libosmocore
diff --git a/src/e b/src/e
deleted file mode 100755
index 4d32bf1..0000000
--- a/src/e
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python3
-import os
-import os.path
-import sys
-import subprocess
-
-base_dir = os.getcwd()
-
-for p in list(os.listdir('.')):
- subdir = os.path.join(base_dir, p)
- if not os.path.isdir(os.path.join(subdir, '.git')):
- continue
- print("\n===== %s =====" % p)
- os.chdir(subdir)
- subprocess.call(sys.argv[1:])
diff --git a/src/g b/src/g
deleted file mode 100755
index bb9b693..0000000
--- a/src/g
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import os
-import subprocess
-
-git_subdirs = []
-
-for subdir in os.listdir():
- if not os.path.isdir(os.path.join(subdir, '.git')):
- continue
-
- print('\n===== %s =====' % subdir)
- sys.stdout.flush()
- subprocess.call(['git', '-C', subdir] + sys.argv[1:])
- sys.stdout.flush()
- sys.stderr.flush()
diff --git a/src/git_branch_summary.py b/src/git_branch_summary.py
deleted file mode 100755
index 9d81f8b..0000000
--- a/src/git_branch_summary.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-
-import sys, subprocess, re
-
-if len(sys.argv) < 2:
- print("Usage: %s <git_dir> [...]\nThis is mostly here for helping the 'st' script." % sys.argv[0])
- exit(1)
-
-interesting_branch_names = [ 'master', 'sysmocom/iu', 'sysmocom/sccp', 'aper-prefix-onto-upstream' ]
-
-re_branch_name = re.compile('^..([^ ]+) .*')
-re_ahead = re.compile('ahead [0-9]+|behind [0-9]+')
-
-def branch_name(line):
- m = re_branch_name.match(line)
- return m.group(1)
-
-interesting = []
-
-def do_one_git(git_dir):
- global interesting
- branch_strs = subprocess.check_output(('git', '-C', git_dir, 'branch', '-vv')).decode('utf-8').splitlines()
- interesting_branches = []
-
- for line in branch_strs:
- name = branch_name(line)
- current_branch = False
- if line.startswith('*'):
- current_branch = True
- elif name not in interesting_branch_names:
- continue
- ahead = re_ahead.findall(line)
- if not ahead and not current_branch:
- continue
- ahead = [x.replace('ahead ', '+').replace('behind ', '-') for x in ahead]
- br = (current_branch, name, ahead)
- if current_branch:
- interesting_branches.insert(0, br)
- else:
- interesting_branches.append(br)
-
- status = subprocess.check_output(('git', '-C', git_dir, 'status')).decode()
- has_mods = 'modified:' in status
-
- interesting.append((git_dir, has_mods, interesting_branches))
-
-
-for git_dir in sys.argv[1:]:
- do_one_git(git_dir)
-
-
-first_col = max([len(git_dir) for git_dir, _, _ in interesting])
-first_col_fmt = '%' + str(first_col) + 's'
-
-for git_dir, has_mods, interesting_branches in interesting:
- strs = [first_col_fmt % git_dir,]
- if has_mods:
- strs.append('MODS')
- for current_branch, name, ahead in interesting_branches:
- br = []
- br.append(name)
- if ahead:
- br.append('[%s]' % '|'.join(ahead))
- strs.append(''.join(br))
-
- print(' '.join(strs))
-
-# vim: shiftwidth=2 expandtab tabstop=2
diff --git a/src/gits b/src/gits
new file mode 100755
index 0000000..5761387
--- /dev/null
+++ b/src/gits
@@ -0,0 +1,362 @@
+#!/usr/bin/env python3
+import sys, subprocess, re, argparse, os
+
+doc = '''gits: conveniently manage several git subdirectories.
+Instead of doing the 'cd foo; git status; cd ../bar; git status' dance, this
+helps to save your time with: status, fetch, rebase, ...
+
+See 'gits help'
+'''
+
+re_status_mods = re.compile('^\t(modified|deleted):.*')
+
+def error(*msgs):
+ sys.stderr.write(''.join(msgs))
+ sys.stderr.write('\n')
+ exit(1)
+
+def usage(*msgs):
+ global doc
+ error(doc, '\n\n', *msgs)
+
+def git(git_dir, *args, may_fail=False, section_marker=False, show_cmd=True):
+ if section_marker:
+ print('\n===== %s =====' % git_dir)
+ sys.stdout.flush()
+
+ cmd = ['git', '-C', git_dir] + list(args)
+ if show_cmd:
+ print(' '.join(cmd))
+
+ rc = subprocess.call(cmd)
+ if rc and not may_fail:
+ error('git returned error! command: git -C %r %s' % (git_dir, ' '.join(repr(arg) for arg in args)))
+
+ if section_marker:
+ sys.stdout.flush()
+ sys.stderr.flush()
+
+def git_output(git_dir, *args):
+ return subprocess.check_output(['git', '-C', git_dir,] + list(args)).decode('utf-8')
+
+def git_branch(git_dir):
+ re_branch_name = re.compile('On branch ([^ ]*)')
+ status = git_output(git_dir, 'status')
+ m = re_branch_name.find(status)
+ if not m:
+ error('No current branch in %r' % git_dir)
+ return m.group(1)
+
+def git_status(git_dir, verbose=False):
+ status_lines = git_output(git_dir, 'status').splitlines()
+ if verbose and len(status_lines):
+ print(status_lines[0])
+
+ on_branch = None
+ branch_status_str = None
+ local_mods = False
+
+ ON_BRANCH = 'On branch '
+ STATUS = 'Your branch'
+
+ for l in status_lines:
+ if l.startswith(ON_BRANCH):
+ if on_branch:
+ error('cannot parse status, more than one branch?')
+ on_branch = l[len(ON_BRANCH):]
+ elif l.startswith(STATUS):
+ if 'Your branch is up to date' in l:
+ branch_status_str = l
+ elif 'Your branch is ahead' in l:
+ branch_status_str = 'ahead: ' + l
+ elif 'Your branch is behind' in l:
+ branch_status_str = 'behind: ' + l
+ elif 'have diverged' in l:
+ branch_status_str = 'diverged: ' + l
+ else:
+ error('unknown status str: %r' % l)
+ else:
+ m = re_status_mods.match(l)
+ if m:
+ local_mods = True
+
+ if verbose:
+ print('%s%s' % (branch_status_str, '\nLOCAL MODS' if local_mods else ''))
+ return (on_branch, branch_status_str, local_mods)
+
+
+def git_branch_summary(git_dir):
+ '''return a list of strings: [branchname, info0, info1,...]'''
+
+ interesting_branch_names = [ 'master' ]
+
+ strs = [git_dir,]
+
+ on_branch, branch_status_str, has_mods = git_status(git_dir)
+
+ if has_mods:
+ strs.append('MODS')
+
+ branch_strs = git_output(git_dir, 'branch', '-vv').splitlines()
+ re_branch_name = re.compile('^..([^ ]+) .*')
+ re_ahead = re.compile('ahead [0-9]+|behind [0-9]+')
+
+ for line in branch_strs:
+ m = re_branch_name.match(line)
+ name = m.group(1)
+
+ current_branch = False
+ if line.startswith('*'):
+ current_branch = True
+ elif name not in interesting_branch_names:
+ continue
+ ahead = re_ahead.findall(line)
+ if not ahead and not current_branch:
+ continue
+ ahead = [x.replace('ahead ', '+').replace('behind ', '-') for x in ahead]
+
+ branch_info = [name]
+ if ahead:
+ branch_info.append('[%s]' % '|'.join(ahead))
+
+ strs.append(''.join(branch_info))
+
+ return strs
+
+def format_summaries(summaries, sep0=' ', sep1=' '):
+ first_col = max([len(row[0]) for row in summaries])
+ first_col_fmt = '%' + str(first_col) + 's'
+
+ lines = []
+ for row in summaries:
+ lines.append('%s%s%s' % (first_col_fmt % row[0], sep0, sep1.join(row[1:])))
+
+ return '\n'.join(lines)
+
+def git_dirs():
+ dirs = []
+ for sub in os.listdir():
+ git_path = os.path.join(sub, '.git')
+ if not os.path.isdir(git_path):
+ continue
+ dirs.append(sub)
+
+ if not dirs:
+ error('No subdirectories found that are git clones')
+
+ return list(sorted(dirs))
+
+def cmd_help():
+ global commands
+ global aliases
+
+ if len(sys.argv) > 2:
+ error('no arguments allowed')
+
+ lines = []
+
+ for name, cmd in commands.items():
+
+ names = [name,]
+
+ for alias_name, alias_for in aliases.items():
+ if alias_for == name:
+ names.append(alias_name)
+
+ line = ['|'.join(names), ]
+
+ func, doc = cmd
+ line.append(doc)
+
+ lines.append(line)
+
+ lines.append(('<git-command>', "Run arbitrary git command in each clone, shortcut for 'do'"))
+ print(format_summaries(lines, ': '))
+
+def print_status():
+ infos = [git_branch_summary(git_dir) for git_dir in git_dirs()]
+ print(format_summaries(infos))
+
+def cmd_status():
+ if len(sys.argv) > 2:
+ error('no arguments allowed')
+ print_status()
+
+def cmd_do(argv=None):
+ if argv is None:
+ argv = sys.argv[2:]
+ for git_dir in git_dirs():
+ git(git_dir, *argv, may_fail=True, section_marker=True)
+
+def cmd_sh():
+ cmd = sys.argv[2:]
+ for git_dir in git_dirs():
+ print('\n===== %s =====' % git_dir)
+ sys.stdout.flush()
+ subprocess.call(cmd, cwd=git_dir)
+ sys.stdout.flush()
+ sys.stderr.flush()
+
+class SkipThisRepos(Exception):
+ pass
+
+def ask(git_dir, *question, valid_answers=('*',)):
+ while True:
+ print('\n' + '\n '.join(question))
+ print(' ' + '\n '.join((
+ 's skip this repos',
+ 't show in tig',
+ 'g show in gitk',
+ )))
+
+ answer = sys.stdin.readline().strip()
+ if answer == 's':
+ raise SkipThisRepos()
+ if answer == 't':
+ subprocess.call(('tig', '--all'), cwd=git_dir)
+ continue
+ if answer == 'g':
+ subprocess.call(('gitk', '--all'), cwd=git_dir)
+ continue
+
+ for v in valid_answers:
+ if v == answer:
+ return answer
+ if v == '*':
+ return answer
+ if v == '+' and len(answer) > 0:
+ return answer
+
+def rebase(git_dir):
+ orig_branch, branch_status_str, local_mods = git_status(git_dir, verbose=True)
+
+ if orig_branch is None:
+ print('Not on a branch: %s' % git_dir)
+ raise SkipThisRepos()
+
+ if local_mods:
+ do_commit = ask(git_dir, 'Local mods.',
+ 'c commit to this branch',
+ '<name> commit to new branch',
+ '<empty> skip')
+
+ if not do_commit:
+ raise SkipThisRepos()
+
+ if do_commit == 'c':
+ git(git_dir, 'commit', '-am', 'wip', may_fail=True)
+ else:
+ git(git_dir, 'checkout', '-b', do_commit)
+ git(git_dir, 'commit', '-am', 'wip', may_fail=True)
+ git(git_dir, 'checkout', orig_branch)
+
+ _, _, local_mods = git_status(git_dir)
+
+ if local_mods:
+ print('There still are local modifications')
+ raise SkipThisRepos()
+
+
+ if branch_status_str is None:
+ print('there is no upstream branch for %r' % orig_branch)
+
+ elif branch_status_str.startswith('behind'):
+ if 'and can be fast-forwarded' in branch_status_str:
+ print('fast-forwarding...')
+ git(git_dir, 'merge')
+ else:
+ do_merge = ask(git_dir, 'Behind. git merge?',
+ "<empty> don't merge",
+ 'ok git merge',
+ valid_answers=('', 'ok')
+ )
+
+ if do_merge == 'ok':
+ git(git_dir, 'merge')
+
+ elif branch_status_str.startswith('ahead'):
+ do_commit = ask(git_dir, 'Ahead. commit to new branch?',
+ '<empty> no',
+ '<name> create new branch',
+ )
+ if do_commit:
+ git(git_dir, 'checkout', '-b', do_commit)
+ git(git_dir, 'commit', '-am', 'wip', may_fail=True)
+ git(git_dir, 'checkout', orig_branch)
+
+ do_reset = ask(git_dir, '%s: git reset --hard origin/%s?' % (orig_branch, orig_branch),
+ '<empty> no',
+ 'OK yes (write OK in caps!)',
+ valid_answers=('', 'OK'))
+
+ if do_reset == 'OK':
+ git(git_dir, 'reset', '--hard', 'origin/%s' % orig_branch)
+
+ elif branch_status_str.startswith('diverged'):
+ do_reset = ask(git_dir, 'Diverged.',
+ '%s: git reset --hard origin/%s?' % (orig_branch, orig_branch),
+ '<empty> no',
+ 'OK yes (write OK in caps!)',
+ valid_answers=('', 'OK'))
+
+ if do_reset == 'OK':
+ git(git_dir, 'reset', '--hard', 'origin/%s' % orig_branch)
+
+ return orig_branch
+
+
+def cmd_rebase():
+ skipped = []
+ for git_dir in git_dirs():
+ try:
+ print('\n\n===== %s =====' % git_dir)
+ sys.stdout.flush()
+
+ branch = rebase(git_dir)
+ if branch != 'master':
+ git(git_dir, 'checkout', 'master')
+ rebase(git_dir)
+ git(git_dir, 'checkout', branch)
+
+ except SkipThisRepos:
+ print('\nSkipping %r' % git_dir)
+ skipped.append(git_dir)
+
+ print('\n\n==========\nrebase done.\n')
+ print_status()
+ if skipped:
+ print('\nskipped: %s' % ' '.join(skipped))
+
+commands = {
+ 'help': (cmd_help, 'List commands.'),
+ 'status': (cmd_status, 'Show a branch summary and indicate modifications.'),
+ 'rebase': (cmd_rebase, 'Interactively merge master and rebase current branch.'),
+ 'sh': (cmd_sh, 'Run arbitrary shell command in each clone'),
+ 'do': (cmd_do, 'Run arbitrary git command in each clone'),
+}
+
+aliases = {
+ 'st': 'status',
+ 'r': 'rebase',
+}
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 2:
+ usage('Pass at least one argument to tell me what to do.')
+
+ command_str = sys.argv[1]
+ alias_for = aliases.get(command_str)
+ if alias_for:
+ command_str = alias_for
+ command = commands.get(command_str)
+
+ if command:
+ func, doc = command
+ func()
+ else:
+ # run arbitrary git command
+ cmd_do(sys.argv[1:])
+
+
+# vim: shiftwidth=2 expandtab tabstop=2
diff --git a/src/s b/src/s
deleted file mode 100755
index f897bc1..0000000
--- a/src/s
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/usr/bin/env bash
-fastforwards=""
-
-Git() {
- echo "git $@"
- git $@
- if [ "$?" != "0" ]; then
- echo "GIT RETURNED ERROR!"
- exit 1
- fi
-}
-
-Git_may_fail() {
- git $@
-}
-
-Git_branch() {
- echo "$(Git -C "$dir" status)" | grep 'On branch' | sed 's/On branch //'
-}
-
-
-gitk_start() {
- if [ -n "$DISPLAY" ]; then
- gitk --all &
- gitk_started="1"
- fi
-}
-
-status() {
- st="$(Git status)"
- mods="$(echo "$st" | grep 'modified:')"
-
- stline="$(echo "$st" | grep '\(behind\|ahead\|up-to-date\|diverged\)')"
-
- echo "$br"
- echo "$stline"
-}
-
-dance() {
- echo
- echo
- br="$(Git_branch)"
-
- echo "$dir"
- cd "$dir"
-
- status
-
- if [ -z "$mods" -a -n "$(echo "$stline" | grep up-to-date)" ]; then
- return 0
- fi
-
- gitk_start
-
- if [ -n "$mods" ]; then
- echo "Local mods"
- echo "$mods"
- echo
- echo "commit to new branch? (enter name, empty = no)"
- read wipbranch
- if [ -n "$wipbranch" ]; then
- Git checkout -b "$wipbranch"
- Git_may_fail commit -am wip
- #Git push --set-upstream origin "$wipbranch"
- Git checkout "$br"
- else
- echo "commit to this branch $br ? (empty = no, 'ok' = yes)"
- read ok
- if [ "x$ok" = xok ]; then
- Git commit -am wip
- #Git push
- fi
- fi
-
- status
-
- if [ -n "$mods" ]; then
- return 0
- fi
- fi
-
- if [ -n "$(echo "$stline" | grep behind)" ]; then
- if [ -n "$(echo "$stline" | grep "and can be fast-forwarded")" ]; then
- echo "fast forwarding..."
- fastforwards="${fastforwards} $dir/$br:$(Git_may_fail rev-parse --short HEAD)"
- ok="ok"
- else
- echo "Behind. git merge? (empty = no, 'ok' = yes)"
- read ok
- fi
- if [ "x$ok" = xok ]; then
- Git merge
- fi
- elif [ -n "$(echo "$stline" | grep ahead)" ]; then
- echo "Ahead. commit to new branch? (enter name, empty = no)"
- read wipbranch
- if [ -n "$wipbranch" ]; then
- Git checkout -b "$wipbranch"
- Git_may_fail commit -am wip
- #Git push --set-upstream origin "$wipbranch"
- Git checkout "$br"
- fi
- echo "$br: git reset --hard origin/$br ? (empty = no, 'OK' IN CAPS = yes)"
- read ok
- if [ "x$ok" = xOK ]; then
- Git reset --hard "origin/$br"
- fi
- return 0
- elif [ -n "$(echo "$stline" | grep diverged)" ]; then
- echo "Diverged. git reset --hard origin/$br ? (empty = no, 'OK' IN CAPS = yes)"
- read ok
- if [ "x$ok" = xOK ]; then
- wipbranch="neels/wip_$(date +%Y%m%d_%H%M)"
- Git checkout -b "$wipbranch"
- Git_may_fail commit -am wip
- Git checkout "$br"
- Git reset --hard "origin/$br"
- fi
- elif [ -z "$(echo "$stline" | grep up-to-date)" ]; then
- echo "Nothing to do."
- echo "$st"
- fi
-
-}
-
-kill_gitk() {
- if [ "$gitk_started" = "1" ]; then
- kill %1
- gitk_started="0"
- fi
-}
-
-
-basedir="$(pwd)"
-gitk_started="0"
-for gitdir in */.git ; do
- cd "$basedir"
- dir="$(dirname "$gitdir")"
-
- orig_branch="$(Git_branch)"
-
- kill_gitk
- dance
- cd "$basedir"
-
- if [ "$orig_branch" != master ]; then
- kill_gitk
- git -C "$dir" checkout master || continue
- dance
- cd "$basedir"
- pwd
- git -C "$dir" checkout "$orig_branch"
- fi
-
-# if [ "$dir" = "openbsc" ]; then
-# kill_gitk
-# Git checkout "sysmocom/iu"
-# dance
-# fi
-
- sleep .1
-
-done
-
-kill_gitk
-
-echo
-echo
-./st
-
-if [ -n "$fastforwards" ]; then
- echo
- echo "FAST-FORWARDED: $fastforwards"
-fi
-
-# vim: shiftwidth=2 expandtab
diff --git a/src/st b/src/st
deleted file mode 100755
index a47de6b..0000000
--- a/src/st
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-git_dirs() {
- for gitdir in */.git ; do
- echo "$(dirname "$gitdir")"
- done
-}
-
-./git_branch_summary.py $(git_dirs)
-# vim: shiftwidth=2 expandtab