Change in osmo-dev[master]: replace src/* git scripts with a single src/gits

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Neels Hofmeyr gerrit-no-reply at lists.osmocom.org
Wed Oct 31 20:40:16 UTC 2018


Neels Hofmeyr has uploaded this change for review. ( https://gerrit.osmocom.org/11560


Change subject: replace src/* git scripts with a single src/gits
......................................................................

replace src/* git scripts with a single src/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
---
M src/README
D src/e
D src/g
D src/git_branch_summary.py
A src/gits
D src/s
D src/st
7 files changed, 353 insertions(+), 298 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-dev refs/changes/60/11560/1

diff --git a/src/README b/src/README
index a2fbe81..f066561 100644
--- a/src/README
+++ b/src/README
@@ -11,12 +11,11 @@
 	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 @@
 
 -----------------------------------------------------------------------------
 
-./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 @@
 
 -----------------------------------------------------------------------------
 
-./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 @@
 
 -----------------------------------------------------------------------------
 
-./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..7d7f253
--- /dev/null
+++ b/src/gits
@@ -0,0 +1,342 @@
+#!/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):
+  status_lines = git_output(git_dir, 'status').splitlines()
+
+  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
+
+  print('branch: %s\n%r%s' % (on_branch, 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 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 cmd_status():
+  if len(sys.argv) > 2:
+    error('no arguments allowed')
+
+  infos = [git_branch_summary(git_dir) for git_dir in git_dirs()]
+  print(format_summaries(infos))
+
+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)
+
+  if local_mods:
+    do_commit = ask(git_dir, 'Local mods.',
+      'c  commit to this branch',
+      '<name>  commit to new branch',)
+
+    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():
+  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)
+
+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

-- 
To view, visit https://gerrit.osmocom.org/11560
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-dev
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: I579e7af26d76d5c5d83b2349695456bc7b54f5a2
Gerrit-Change-Number: 11560
Gerrit-PatchSet: 1
Gerrit-Owner: Neels Hofmeyr <nhofmeyr at sysmocom.de>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20181031/5fd456a9/attachment.htm>


More information about the gerrit-log mailing list