diff options
author | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2013-09-17 16:59:40 +0000 |
---|---|---|
committer | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2013-09-17 16:59:40 +0000 |
commit | fd32e4b7920ddd3e5bc983b731c1712dafb37311 (patch) | |
tree | 04d527ce99b5636d2a0af80d541e1c281248b70a | |
parent | 86209e1f346fd24119f31872b589a70533523585 (diff) | |
parent | 601f39bca09fa52acadec3b908f29aecc241e868 (diff) | |
download | morph-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.py | 37 | ||||
-rw-r--r-- | morphlib/gitdir_tests.py | 15 | ||||
-rw-r--r-- | morphlib/plugins/branch_and_merge_new_plugin.py | 70 | ||||
-rw-r--r-- | morphlib/plugins/branch_and_merge_plugin.py | 2 | ||||
-rw-r--r-- | morphlib/sysbranchdir.py | 10 | ||||
-rw-r--r-- | morphlib/sysbranchdir_tests.py | 2 | ||||
-rw-r--r-- | morphlib/util.py | 35 | ||||
-rw-r--r-- | morphlib/workspace.py | 5 | ||||
-rw-r--r-- | morphlib/workspace_tests.py | 10 |
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) |