summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-09-17 16:59:40 +0000
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-09-17 16:59:40 +0000
commitfd32e4b7920ddd3e5bc983b731c1712dafb37311 (patch)
tree04d527ce99b5636d2a0af80d541e1c281248b70a
parent86209e1f346fd24119f31872b589a70533523585 (diff)
parent601f39bca09fa52acadec3b908f29aecc241e868 (diff)
downloadmorph-fd32e4b7920ddd3e5bc983b731c1712dafb37311.tar.gz
Merge remote-tracking branch 'origin/baserock/richardmaw/S8511/refactor-status-v2'
Reviewed-by: Lars Wirzenius Reviewed-by: Jonathan Maw
-rw-r--r--morphlib/gitdir.py37
-rw-r--r--morphlib/gitdir_tests.py15
-rw-r--r--morphlib/plugins/branch_and_merge_new_plugin.py70
-rw-r--r--morphlib/plugins/branch_and_merge_plugin.py2
-rw-r--r--morphlib/sysbranchdir.py10
-rw-r--r--morphlib/sysbranchdir_tests.py2
-rw-r--r--morphlib/util.py35
-rw-r--r--morphlib/workspace.py5
-rw-r--r--morphlib/workspace_tests.py10
9 files changed, 164 insertions, 22 deletions
diff --git a/morphlib/gitdir.py b/morphlib/gitdir.py
index cb247303..7ee64f36 100644
--- a/morphlib/gitdir.py
+++ b/morphlib/gitdir.py
@@ -16,6 +16,7 @@
# =*= License: GPL-2 =*=
+import collections
import cliapp
import glob
import os
@@ -195,6 +196,42 @@ class GitDirectory(object):
tree = self._rev_parse_tree(ref)
return self.cat_file('blob', tree, filename)
+ @property
+ def HEAD(self):
+ output = self._runcmd(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
+ return output.strip()
+
+ def _get_status(self):
+ '''Runs git status and formats its output into something more useful.
+
+ This runs git status such that unusual filenames are preserved
+ and returns its output in a sequence of (status_code, to_path,
+ from_path).
+
+ from_path is None unless the status_code says there was a rename,
+ in which case it is the path it was renamed from.
+
+ Untracked and ignored changes are also included in the output,
+ their status codes are '??' and '!!' respectively.
+
+ '''
+ status = self._runcmd(['git', 'status', '-z', '--ignored'])
+ tokens = collections.deque(status.split('\0'))
+ while True:
+ tok = tokens.popleft()
+ # Terminates with an empty token, since status ends with a \0
+ if not tok:
+ return
+
+ code = tok[:2]
+ to_path = tok[3:]
+ yield code, to_path, tokens.popleft() if code[0] == 'R' else None
+
+ def get_uncommitted_changes(self):
+ for code, to_path, from_path in self._get_status():
+ if code not in ('??', '!!'):
+ yield code, to_path, from_path
+
def init(dirname):
'''Initialise a new git repository.'''
diff --git a/morphlib/gitdir_tests.py b/morphlib/gitdir_tests.py
index 175b8ee7..803f1b3e 100644
--- a/morphlib/gitdir_tests.py
+++ b/morphlib/gitdir_tests.py
@@ -137,3 +137,18 @@ class GitDirectoryContentsTests(unittest.TestCase):
gd = morphlib.gitdir.GitDirectory(self.dirname)
self.assertRaises(morphlib.gitdir.InvalidRefError,
gd.read_file, 'bar', 'no-such-ref')
+
+ def test_HEAD(self):
+ gd = morphlib.gitdir.GitDirectory(self.dirname)
+ self.assertEqual(gd.HEAD, 'master')
+
+ gd.branch('foo', 'master')
+ self.assertEqual(gd.HEAD, 'master')
+
+ gd.checkout('foo')
+ self.assertEqual(gd.HEAD, 'foo')
+
+ def test_uncommitted_changes(self):
+ gd = morphlib.gitdir.GitDirectory(self.dirname)
+ self.assertEqual(sorted(gd.get_uncommitted_changes()),
+ [(' D', 'foo', None)])
diff --git a/morphlib/plugins/branch_and_merge_new_plugin.py b/morphlib/plugins/branch_and_merge_new_plugin.py
index 66231de9..7710f309 100644
--- a/morphlib/plugins/branch_and_merge_new_plugin.py
+++ b/morphlib/plugins/branch_and_merge_new_plugin.py
@@ -53,6 +53,8 @@ class SimpleBranchAndMergePlugin(cliapp.Plugin):
'show-branch-root', self.show_branch_root, arg_synopsis='')
self.app.add_subcommand('foreach', self.foreach,
arg_synopsis='-- COMMAND [ARGS...]')
+ self.app.add_subcommand('status', self.status,
+ arg_synopsis='')
def disable(self):
pass
@@ -741,3 +743,71 @@ class SimpleBranchAndMergePlugin(cliapp.Plugin):
# Write morphologies back out again.
self._save_dirty_morphologies(loader, sb, morphs.morphologies)
+
+ def status(self, args):
+ '''Show information about the current system branch or workspace
+
+ This shows the status of every local git repository of the
+ current system branch. This is similar to running `git status`
+ in each repository separately.
+
+ If run in a Morph workspace, but not in a system branch checkout,
+ it lists all checked out system branches in the workspace.
+
+ '''
+
+ if args:
+ raise cliapp.AppException('morph status takes no arguments')
+
+ ws = morphlib.workspace.open('.')
+ try:
+ sb = morphlib.sysbranchdir.open_from_within('.')
+ except morphlib.sysbranchdir.NotInSystemBranch:
+ self._workspace_status(ws)
+ else:
+ self._branch_status(ws, sb)
+
+ def _workspace_status(self, ws):
+ '''Show information about the current workspace
+
+ This lists all checked out system branches in the workspace.
+
+ '''
+ self.app.output.write("System branches in current workspace:\n")
+ branches = sorted(ws.list_system_branches(),
+ key=lambda x: x.root_directory)
+ for sb in branches:
+ self.app.output.write(" %s\n" % sb.get_config('branch.name'))
+
+ def _branch_status(self, ws, sb):
+ '''Show information about the current branch
+
+ This shows the status of every local git repository of the
+ current system branch. This is similar to running `git status`
+ in each repository separately.
+
+ '''
+ branch = sb.get_config('branch.name')
+ root = sb.get_config('branch.root')
+
+ self.app.output.write("On branch %s, root %s\n" % (branch, root))
+
+ has_uncommitted_changes = False
+ for gd in sorted(sb.list_git_directories(), key=lambda x: x.dirname):
+ try:
+ repo = gd.get_config('morph.repository')
+ except cliapp.AppException:
+ self.app.output.write(
+ ' %s: not part of system branch\n' % gd.dirname)
+ # TODO: make this less vulnerable to a branch using
+ # refs/heads/foo instead of foo
+ head = gd.HEAD
+ if head != branch:
+ self.app.output.write(
+ ' %s: unexpected ref checked out %r\n' % (repo, head))
+ if any(gd.get_uncommitted_changes()):
+ has_uncommitted_changes = True
+ self.app.output.write(' %s: uncommitted changes\n' % repo)
+
+ if not has_uncommitted_changes:
+ self.app.output.write("\nNo repos have outstanding changes.\n")
diff --git a/morphlib/plugins/branch_and_merge_plugin.py b/morphlib/plugins/branch_and_merge_plugin.py
index 671071b6..37d5e40c 100644
--- a/morphlib/plugins/branch_and_merge_plugin.py
+++ b/morphlib/plugins/branch_and_merge_plugin.py
@@ -67,7 +67,7 @@ class BranchAndMergePlugin(cliapp.Plugin):
'tag', self.tag, arg_synopsis='TAG-NAME -- [GIT-COMMIT-ARG...]')
self.app.add_subcommand('build', self.build,
arg_synopsis='SYSTEM')
- self.app.add_subcommand('status', self.status)
+ self.app.add_subcommand('old-status', self.status)
self.app.add_subcommand('branch-from-image', self.branch_from_image,
arg_synopsis='REPO BRANCH')
group_branch = 'Branching Options'
diff --git a/morphlib/sysbranchdir.py b/morphlib/sysbranchdir.py
index 0b3c859a..a05ca52e 100644
--- a/morphlib/sysbranchdir.py
+++ b/morphlib/sysbranchdir.py
@@ -156,13 +156,9 @@ class SystemBranchDirectory(object):
'''
- gitdirs = []
- for dirname, subdirs, filenames in os.walk(self.root_directory):
- if os.path.isdir(os.path.join(dirname, '.git')):
- del subdirs[:]
- gitdirs.append(morphlib.gitdir.GitDirectory(dirname))
-
- return gitdirs
+ return (morphlib.gitdir.GitDirectory(dirname)
+ for dirname in
+ morphlib.util.find_leaves(self.root_directory, '.git'))
def create(root_directory, root_repository_url, system_branch_name):
diff --git a/morphlib/sysbranchdir_tests.py b/morphlib/sysbranchdir_tests.py
index 7ee04c7d..7ec8ef5c 100644
--- a/morphlib/sysbranchdir_tests.py
+++ b/morphlib/sysbranchdir_tests.py
@@ -213,7 +213,7 @@ class SystemBranchDirectoryTests(unittest.TestCase):
cached_repo = self.create_fake_cached_repo()
sb.clone_cached_repo(cached_repo, 'master')
- gd_list = sb.list_git_directories()
+ gd_list = list(sb.list_git_directories())
self.assertEqual(len(gd_list), 1)
self.assertEqual(
gd_list[0].dirname,
diff --git a/morphlib/util.py b/morphlib/util.py
index 22288cac..19c0046f 100644
--- a/morphlib/util.py
+++ b/morphlib/util.py
@@ -306,26 +306,35 @@ def find_root(dirname, subdir_name):
return dirname
+def find_leaves(search_dir, subdir_name):
+ '''This is like find_root, except it looks towards leaves.
+
+ The directory tree, starting at search_dir is traversed.
+
+ If a directory has a subdirectory called subdir_name, then
+ the directory is returned.
+
+ It does not recurse into a leaf's subdirectories.
+
+ '''
+
+ for dirname, subdirs, filenames in os.walk(search_dir):
+ if subdir_name in subdirs:
+ del subdirs[:]
+ yield dirname
+
+
def find_leaf(dirname, subdir_name):
'''This is like find_root, except it looks towards leaves.
- It only looks in a subdirectory if it is the only subdirectory.
If there are no subdirectories, or more than one, fail.
- (Subdirectories whose name starts with a dot are ignored for this.)
'''
- while True:
- if os.path.exists(os.path.join(dirname, subdir_name)):
- return dirname
- pathnames = [
- os.path.join(dirname, x)
- for x in os.listdir(dirname)
- if not x.startswith('.')]
- subdirs = [x for x in pathnames if os.path.isdir(x)]
- if len(subdirs) != 1:
- return None
- dirname = subdirs[0]
+ leaves = list(find_leaves(dirname, subdir_name))
+ if len(leaves) == 1:
+ return leaves[0]
+ return None
class EnvironmentAlreadySetError(morphlib.Error):
diff --git a/morphlib/workspace.py b/morphlib/workspace.py
index 93f699e6..27ccbe65 100644
--- a/morphlib/workspace.py
+++ b/morphlib/workspace.py
@@ -87,6 +87,11 @@ class Workspace(object):
dirname, root_repository_url, system_branch_name)
return sb
+ def list_system_branches(self):
+ return (morphlib.sysbranchdir.open(dirname)
+ for dirname in
+ morphlib.util.find_leaves(self.root, '.morph-system-branch'))
+
def open(dirname):
'''Open an existing workspace.
diff --git a/morphlib/workspace_tests.py b/morphlib/workspace_tests.py
index 83b5e54f..b25be35e 100644
--- a/morphlib/workspace_tests.py
+++ b/morphlib/workspace_tests.py
@@ -99,3 +99,13 @@ class WorkspaceTests(unittest.TestCase):
sb = ws.create_system_branch_directory(url, branch)
self.assertTrue(type(sb), morphlib.sysbranchdir.SystemBranchDirectory)
+ def test_lists_created_system_branches(self):
+ self.create_it()
+ ws = morphlib.workspace.open(self.workspace_dir)
+
+ branches = ["branch/1", "branch/2"]
+ for branch in branches:
+ ws.create_system_branch_directory('test:morphs', branch)
+ self.assertEqual(sorted(sb.system_branch_name
+ for sb in ws.list_system_branches()),
+ branches)