def get_workspace_location(override):
    from . import __version__
    wsdir = find_workspace(override)
    if wsdir is not None:
        wstype, wsversion = detect_workspace_type(wsdir)
        if wstype == 3:
            return wsdir
        msg("catkin workspace detected in @{cf}%s@|\n\n" % escape(wsdir))
        if wstype == -1:
            msg(
                "I found a catkin workspace, but %(error_msg)s\n\n"
                "You can delete any corrupted settings and reinitialize the "
                "workspace for rosrepo with the command\n\n"
                "    @!rosrepo init --reset %(path)s@|\n\n"
                % {"path": escape(wsdir), "error_msg": wsversion}
            )
        if wstype == 0:
            msg(
                "I found a catkin workspace, but it is not configured with rosrepo.\n\n"
                "If you wish to use rosrepo with this workspace, run the command\n\n"
                "    @!rosrepo init %(path)s@|\n\n"
                % {"path": escape(wsdir)}
            )
        if wstype == 4:
            msg(
                "This catkin workspace has been configured by a newer version of rosrepo, "
                "please upgrade to version @{cf}%(new_version)s@| or newer.\n\n"
                "If you want to revert the workspace back to this version (@{cf}%(old_version)s@|), "
                "you can reset all settings with\n\n"
                "    @!rosrepo init --reset %(path)s@|\n\n"
                "@!@{yf}WARNING@|: Please make a backup before doing this!\n\n"
                % {"path": escape(wsdir), "new_version": escape(wsversion), "old_version": __version__}
            )
        if wstype == 1 or wstype == 2:
            msg(
                "This catkin workspace has been configured by rosrepo @{cf}%(old_version)s@|, "
                "but you are currently running version @{cf}%(new_version)s@|\n\n"
                "If you wish to use the new version of rosrepo, you need to reinitialize the "
                "workspace with the command\n\n"
                "    @!rosrepo init %(path)s@|\n\n"
                % {"old_version": escape(wsversion), "new_version": __version__, "path": escape(wsdir)}
            )
        if override is None:
            msg(
                "If this is not the workspace location you were looking for, try "
                "the @{cf}--workspace@| option to override the automatic detection.\n\n"
            )
    else:
        if override is not None:
            msg(
                "There is no catkin workspace in %(path)s\n\n"
                "You can create a new workspace there by running\n\n"
                "    @!rosrepo init %(path)s@|\n\n"
                "If you are really sure that there is a workspace there already, "
                "it is possible that the marker file has been deleted by accident. "
                "In that case, the above command will restore your workspace.\n\n"
                % {"path": escape(override)}
            )
        else:
            msg(
                "I cannot find any catkin workspace.\n\n"
                "Please make sure that you have sourced the @{cf}setup.bash@| file of "
                "your workspace or use the @{cf}--workspace@| option to override the "
                "automatic detection. If you have never created a workspace yet, "
                "you can initialize one in your home directory with\n\n"
                "    @!rosrepo init %s/ros@|\n\n"
                % escape(os.path.expanduser("~"))
            )
    fatal("valid workspace location required\n") def get_workspace_state(wsdir, config=None, cache=None, offline_mode=False, verbose=True, ws_state=None, flags=WSFL_ALL):
    if ws_state is None:
        ws_state = WorkspaceState()
    if config is None:
        config = Config(wsdir)
    if cache is None:
        cache = Cache(wsdir)
    link_projects = False
    if flags & WSFL_WS_PACKAGES:
        link_projects = True
        ws_state.ws_packages = find_catkin_packages(os.path.join(wsdir, "src"), cache=cache)
        for name, pkg_list in iteritems(ws_state.ws_packages):
            if len(pkg_list) > 1:
                msg("You have multiple versions of the package @{cf}%s@| in your workspace:\n\n" % escape(name))
                for pkg in pkg_list:
                    msg("     - @{cf}%s@|\n" % escape(os.path.join(wsdir, "src", pkg.workspace_path)))
                    if is_deprecated_package(pkg.manifest):
                        msg("       @{rf}(deprecated)@|\n")
                msg(
                    "\n"
                    "Please remove all but one of the versions or place a @{cf}CATKIN_IGNORE@| file "
                    "in their path to disable them.\n\n"
                )
                fatal("workspace has conflicting packages\n")
    if flags & WSFL_ROS_ROOT_PACKAGES:
        ros_rootdir = find_ros_root(config.get("ros_root", None))
        if ros_rootdir is not None:
            ws_state.ros_root_packages = find_catkin_packages(ros_rootdir, cache=cache, cache_id="ros_root_packages")
        else:
            ws_state.ros_root_packages = {}
    if flags & WSFL_REMOTE_PROJECTS:
        ws_state.remote_projects = get_gitlab_projects(wsdir, config, cache=cache, offline_mode=offline_mode, verbose=verbose)
    if flags & WSFL_WS_PROJECTS and ws_state.remote_projects is not None:
        link_projects = True
        ws_state.ws_projects, ws_state.other_git = find_cloned_gitlab_projects(ws_state.remote_projects, os.path.join(wsdir, "src"))
    if flags & WSFL_REMOTE_PACKAGES and ws_state.remote_projects is not None:
        ws_state.remote_packages = find_catkin_packages_from_gitlab_projects(ws_state.remote_projects)
    if link_projects and ws_state.ws_packages is not None and ws_state.ws_projects is not None:
        for _, pkg_list in iteritems(ws_state.ws_packages):
            for pkg in pkg_list:
                for prj in ws_state.ws_projects:
                    if path_has_prefix(pkg.workspace_path, prj.workspace_path):
                        pkg.project = prj
                        break
    return ws_state def find_catkin_packages(srcdir, subdir=None, cache=None, cache_id="workspace_packages"):
    cached_paths = {}
    cache_update = False
    if cache is not None:
        cached_paths = cache.get_object(cache_id, WORKSPACE_PACKAGE_CACHE_VERSION, cached_paths)
    package_paths = []
    base_path = srcdir if subdir is None else os.path.join(srcdir, subdir)
    for curdir, subdirs, files in os_walk(base_path, followlinks=True):
        if "CATKIN_IGNORE" in files:
            del subdirs[:]
            continue
        if PACKAGE_MANIFEST_FILENAME in files:
            package_paths.append(os.path.relpath(curdir, srcdir))
            del subdirs[:]
            continue
        subdirs = [d for d in subdirs if not d.startswith(".")]
    result = {}
    discovered_paths = {}
    for path in package_paths:
        try:
            cur_ts = os.path.getmtime(os.path.join(srcdir, path, PACKAGE_MANIFEST_FILENAME))
            manifest = None
            if path in cached_paths:
                old_ts = cached_paths[path]["t"]
                if old_ts == cur_ts:
                    manifest = cached_paths[path]["m"]
            if manifest is None:
                cache_update = True
                manifest = parse_package(os.path.join(srcdir, path, PACKAGE_MANIFEST_FILENAME))
            if manifest.name not in result:
                result[manifest.name] = []
            result[manifest.name].append(Package(manifest=manifest, workspace_path=path))
            discovered_paths[path] = {"t": cur_ts, "m": manifest}
        except InvalidPackage as e:
            msg(str(e) + "\n")
            fatal("invalid package in workspace")
    if subdir is not None:
        for path, entry in iteritems(cached_paths):
            if not path_has_prefix(path, subdir):
                discovered_paths[path] = entry
    if cache is not None:
        if cache_update or len(cached_paths) != len(discovered_paths):
            cache.set_object(cache_id, WORKSPACE_PACKAGE_CACHE_VERSION, discovered_paths)
    return result def resolve_this(wsdir, ws_state):
    result = set()
    curdir = os.path.relpath(os.getcwd(), os.path.join(wsdir, "src"))
    for name, pkg_list in iteritems(ws_state.ws_packages):
        for pkg in pkg_list:
            if path_has_prefix(curdir, pkg.workspace_path):
                result.add(name)
    if not result:
        fatal("no package in this folder")
    return result class Config(object):
    def __init__(self, wsdir, read_only=False):
        self.config_dir = os.path.join(wsdir, ".rosrepo")
        self.config_file = os.path.join(self.config_dir, "config")
        self.read_only = read_only
        if os.path.isfile(self.config_file):
            try:
                with open(self.config_file, "rb") as f:
                    self._data = yaml_load(f)
            except (OSError, IOError, YAMLError):
                raise ConfigError("unreadable configuration file")
            if not isinstance(self._data, dict):
                raise ConfigError("invalid configuration file")
            if "version" not in self._data:
                raise ConfigError("missing configuration version number")
            current = Version(__version__)
            try:
                stored = Version(self._data["version"])
            except (TypeError, ValueError):
                raise ConfigError("invalid configuration version number")
            if stored < current and not read_only:
                self._migrate(stored)
            if stored.version[:2] > current.version[:2] and not read_only:
                raise ConfigError("workspace was configured by newer version of rosrepo")
        else:
            self._data = {"version": __version__}

    def write(self):
        if self.read_only:
            raise ConfigError("cannot write config file marked as read only")
        try:
            makedirs(self.config_dir)
            write_atomic(self.config_file, yaml_dump(self._data, encoding="UTF-8", default_flow_style=False))
        except (IOError, OSError):
            raise ConfigError("cannnot write config file %s" % self.config_file)

    def _migrate(self, old_version):
        if old_version <= Version("3.0.25"):
            if "crawl_depth" in self._data:
                self._data["gitlab_crawl_depth"] = self._data["crawl_depth"]
                del self._data["crawl_depth"]
        self._data["version"] = __version__
        try:
            self.write()
        except ConfigError:
            warning("cannot write updated configuration\n")

    def set_default(self, key, value):
        if self.read_only:
            raise ConfigError("cannot change read-only configuration")
        if key not in self._data:
            self._data[key] = value

    def get(self, key, default=None):
        return self._data.get(key, default)

    def __getitem__(self, key):
        return self._data[key]

    def __setitem__(self, key, value):
        if self.read_only:
            raise ConfigError("cannot change read-only configuration")
        self._data[key] = value

    def __delitem__(self, key):
        if self.read_only:
            raise ConfigError("cannot change read-only configuration")
        if key in self._data:
            del self._data[key]

    def __iter__(self):
        return self._data.__iter__()

    def __len__(self):
        return len(self._data)

    def __contains__(self, key):
        return key in self._data class Cache(object):

    def __init__(self, wsdir):
        self.cache_dir = os.path.join(wsdir, ".rosrepo", "cache")
        self.preloaded = {}

    def get_object(self, name, version, default=None):
        if name in self.preloaded:
            if self.preloaded[name].version == version:
                return self.preloaded[name].obj
            return default
        try:
            with open(os.path.join(self.cache_dir, name), "rb") as f:
                cache_file = pickle.loads(zlib.decompress(f.read()))
        except Exception:
            return default
        if not isinstance(cache_file, CacheFile):
            return default
        self.preloaded[name] = cache_file
        if cache_file.version != version:
            return default
        return cache_file.obj

    def set_object(self, name, version, obj):
        cache_file = CacheFile(version=version, obj=obj)
        makedirs(self.cache_dir)
        filepath = os.path.join(self.cache_dir, name)
        write_atomic(filepath, zlib.compress(pickle.dumps(cache_file, -1)), ignore_fail=True)
        self.preloaded[name] = cache_file

    def reset_object(self, name):
        if name in self.preloaded:
            del self.preloaded[name]
        try:
            os.unlink(os.path.join(self.cache_dir, name))
        except OSError:
            pass def find_dependees(packages, ws_state, auto_resolve=False, ignore_missing=False, recursive=True, force_workspace=True):
    P_REMOTE = 1
    P_WS = 2
    P_ROS_ROOT = 3

    def find_package_candidates(name, ws_state, resolver_msgs):
        # Returns a list of packages which satisfy the given dependency
        if name in ws_state.ws_packages:
            # If the package is in the workspace, it will be used unconditionally
            return [(ws_state.ws_packages[name][0], P_WS)]
        result = []
        if name in ws_state.ros_root_packages:
            result.append((ws_state.ros_root_packages[name][0], P_ROS_ROOT))
        if name in ws_state.remote_packages:
            remotes = ws_state.remote_packages[name]
            if len(remotes) > 1 and not auto_resolve:
                # If desired, let the user pick one
                desired = pick_dependency_resolution(name, remotes)
                if desired is not None:
                    remotes = [desired]
            for pkg in remotes:
                result.append((pkg, P_REMOTE))
        return sorted(result, key=lambda x: (ManifestVersion(x[0].manifest.version), x[1]), reverse=True)

    def try_resolve(queue, depends=None, system_depends=None, conflicts=None):
        if depends is None:
            depends = {}
        if system_depends is None:
            system_depends = set()
        if conflicts is None:
            conflicts = {}
        while len(queue) > 0:
            root_depender, depender, name = queue.pop()
            if name not in depends and name not in conflicts and name not in system_depends:
                resolver_msgs = []
                if root_depender is not None and root_depender != depender:
                    resolver_msgs.append("is needed to resolve dependencies of package @{cf}%s@|" % escape(root_depender))
                if depender is not None:
                    resolver_msgs.append("is dependee of package @{cf}%s@|" % escape(depender))
                if root_depender is None:
                    root_depender = name
                candidates = find_package_candidates(name, ws_state, resolver_msgs)
                for pkg, source in candidates:
                    if source not in [P_WS, P_REMOTE] and depender is None and force_workspace:
                        continue  # Ignore other sources if primary depender should be a workspace package
                    if source == P_REMOTE:
                        if pkg.project in ws_state.ws_projects:
                            resolver_msgs.append("missing from @{cf}%s/@| (maybe you need to @{cf}git pull@|?)" % escape(pkg.project.workspace_path))
                            continue  # Fail, try next
                        # Check all other packages in the same project for conflicts
                        ok = True
                        for other in pkg.project.packages:
                            # Is the other package in the workspace already?
                            if other.manifest.name in ws_state.ws_packages:
                                resolver_msgs.append("cannot be cloned from @{cf}%s@| because package @{cf}%s@| is in the workspace already" % (escape(pkg.project.name), escape(other.manifest.name)))
                                ok = False
                                break  # Fail
                            # If not, check if we already decided to download
                            # the package from another project
                            if other.manifest.name in depends:
                                # Is it the same project?
                                if other.project != pkg.project:
                                    resolver_msgs.append("cannot be cloned from @{cf}%s@| because it contains package @{cf}%s@| which will be cloned from @{cf}%s@|" % (escape(pkg.project.name), escape(other.manifest.name), escape(other.project.name)))
                                    ok = False
                                    break  # Fail
                        if not ok:
                            continue  # Fail, try next
                    # If we got to this point, the package can be used to resolve the dependency
                    if source in [P_WS, P_REMOTE]:
                        depends[name] = pkg
                        if recursive or depender is None:
                            recursive_depends = pkg.manifest.buildtool_depends + pkg.manifest.build_depends + pkg.manifest.run_depends + pkg.manifest.test_depends
                            queue += [(root_depender, name, p.name) for p in recursive_depends]
                    else:
                        system_depends.add(name)
                    break  # Success, do not check more candidates
                else:
                    if not candidates:
                        resolver_msgs.append("no such package available")
                    else:
                        resolver_msgs.append("not in workspace and cannot be cloned from Gitlab")
                    # No candidate matched, but maybe we got lucky and it's a known system dependency
                    # However, if it is requested explicitly, we won't use system dependencies
                    if depender is not None and name in get_rosdep():
                        system_depends.add(name)
                    else:
                        # We cannot resolve this
                        # If the package is unknown, i.e. does not have any installation candidates,
                        # we may choose to ignore it, if so instructed
                        if candidates or not ignore_missing:
                            conflicts[name] = resolver_msgs
        return depends, system_depends, conflicts
    queue = [(None, None, name) for name in packages]
    depends, system_depends, conflicts = try_resolve(queue)
    return depends, system_depends, conflicts def resolve_system_depends(ws_state, system_depends, missing_only=False):
    global _resolve_warn_once
    resolved = set()
    if get_system_package_manager().installer is None:
        if not _resolve_warn_once:
            error("cannot resolve system dependencies for this system\n")
            _resolve_warn_once = True
        return set()
    for dep in system_depends:
        if dep not in ws_state.ros_root_packages:  # This deals with ROS source installs
            rosdep = get_rosdep()
            if not rosdep.ok():
                if not _resolve_warn_once:
                    error("cannot resolve system dependencies without rosdep\n")
                    _resolve_warn_once = True
                return set()
            try:
                from rosdep2.lookup import ResolutionError
            except ImportError:
                class ResolutionError(Exception):
                    pass
            try:
                installer, resolved_deps = rosdep.resolve(dep)
                for d in resolved_deps:
                    if installer == get_system_package_manager().installer:
                        if hasattr(d, "package"):
                            resolved.add(d.package)
                        else:
                            resolved.add(d)
                    else:
                        warning("unsupported installer '%s': ignoring package '%s'\n" % (installer, dep))
            except ResolutionError:
                warning("cannot resolve system package: ignoring package '%s'\n" % dep)
    if missing_only:
        resolved -= get_system_package_manager().installed_packages
    return resolved class TableView(object):
    def __init__(self, *args, **kwargs):
        self.columns = args
        self.expand = kwargs.get("expand", False)
        if not self.columns:
            self.width = [1, 1]
        else:
            self.width = [max(1, len(remove_ansi(color_fmt(c)))) for c in self.columns]
        self.rows = []

    def add_row(self, *args):
        row = [r if isinstance(r, list) or isinstance(r, tuple) else (r,) for r in args]
        row = [r if isinstance(r, tuple) or len(r) > 0 else [""] for r in row]  # Handle special case with empty lists
        assert len(row) == len(self.width)
        self.rows.append(row)
        self.width = [max(w, *(len(remove_ansi(color_fmt(r))) for r in rs)) for w, rs in zip(self.width, row)]

    def add_separator(self):
        self.rows.append(None)

    def empty(self):
        return len(self.rows) == 0

    def sort(self, column_index):
        self.rows.sort(key=lambda x: x[column_index])

    def write(self, fd=None, use_color=None):
        width = self.width
        actual_width = sum(width) + 3 * len(width) - 1
        try:
            total_width = get_terminal_size()[0]
        except OSError:
            total_width = None
        if fd is None:
            fd = sys.stdout
        if use_color is None:
            use_color = isatty(fd)
        if total_width is not None:
            if self.expand and actual_width < total_width:
                width[-1] += total_width - actual_width
                actual_width = total_width
            while actual_width > total_width:
                max_width = max(width)
                if max_width == 1:
                    break
                for i in range(len(width)):
                    if width[i] == max_width:
                        width[i] -= 1
                        actual_width -= 1
                        break
        if self.columns:
            fmt = color_fmt(" " + (" @{pf}" + HSEP + "@| ").join(["%s"] * len(width)) + "\n", use_color=use_color)
            fd.write(color_fmt("@{pf}" + HTHICK + THEAD.join([HTHICK * w for w in width]) + HTHICK + "\n", use_color=use_color))
            sep = color_fmt("@{pf}" + HTHIN + XSEP.join([HTHIN * w for w in width]) + HTHIN + "\n", use_color=use_color)
            end = color_fmt("@{pf}" + HTHICK + TFOOT.join([HTHICK * w for w in width]) + HTHICK + "\n", use_color=use_color)
            fd.write(fmt % tuple(pad_ansi_text(color_fmt("@!%s" % c, use_color=use_color), w) for c, w in zip(self.columns, width)))
        else:
            fmt = " %s   %s\n"
            sep = color_fmt("@{pf}" + (HTHIN * actual_width) + "@|\n", use_color=use_color)
            end = sep
        fd.write(sep)
        for row in self.rows:
            if row is None:
                fd.write(sep)
                continue
            for line in zip_longest(*row, fillvalue=""):
                chunks = (slice_ansi_text(color_fmt(r, use_color=use_color), w) for r, w in zip(line, width))
                for chunk in zip_longest(*chunks):
                    fd.write(fmt % tuple(textify(r, fd=fd) if r is not None else " " * w for r, w in zip(chunk, width)))
        fd.write(end) def msg(text, max_width=None, use_color=None, wrap=True, fd=None, **wrap_args):
    from .terminal_color import ansi
    if fd is None:
        fd = sys.stderr
    if use_color is None:
        use_color = isatty(fd)
    ansi_text = color_fmt(text, use_color=use_color)
    if wrap:
        try:
            if max_width is None:
                max_width, _ = get_terminal_size()
        except OSError:
            pass
    fd.write(wrap_ansi_text(ansi_text, max_width, **wrap_args) + (ansi('reset') if use_color else "")) def warning(text, use_color=None):
    prog = "rosrepo"
    msg("@!@{yf}%s: warning: %s" % (prog, text), use_color=use_color, indent_next=len(prog) + 11) def error(text, use_color=None):
    prog = "rosrepo"
    msg("@!@{rf}%s: error: %s" % (prog, text), indent_next=len(prog) + 9) def fatal(text):
    raise UserError(text) def escape(msg):
    return msg.replace("@", "@@") def show_conflicts(conflicts):
    for name in sorted(conflicts.keys()):
        error("cannot use package '%s'\n" % escape(name))
        for reason in conflicts[name]:
            msg("   - %s\n" % reason, indent_next=5) def show_missing_system_depends(missing):
    if missing:
        from .resolver import get_system_package_manager
        msg(
            "You need to install additional resources on this computer to satisfy all dependencies. "
            "Please run the following command:\n\n"
        )
        msg("@!" + get_system_package_manager().installer_cmd + " " + " ".join(sorted(list(missing))), indent_first=4, indent_next=len(get_system_package_manager().installer_cmd) + 5, suffix=" \\")
        msg("\n\n") def textify(s, fd=None):
    if fd is None:
        fd = sys.stdout
    if hasattr(s, "encode"):
        return s.encode(fd.encoding if hasattr(fd, "encoding") and fd.encoding else "UTF-8")
    return str(s) LARROW = None RARROW = None FF_LARROW = None FF_RARROW = None def yaml_load(stream, Loader=SafeLoader):
def yaml_dump(data, stream=None, Dumper=SafeDumper, **kwargs):
    def __init__(self, *args, **kwargs):
    def __str__(self):
    def __getitem__(self, item):
    def __setitem__(self, item, value):
    def __len__(self):
def is_deprecated_package(manifest):
def deprecated_package_info(manifest):
def path_has_prefix(path, prefix):
def has_package_path(obj, paths):
def env_path_list_contains(path_list, path):
def makedirs(path):
def write_atomic(filepath, data, mode=0o644, ignore_fail=False):
def isatty(fd):
def get_terminal_size():
def find_program(program):
    def is_exe(fpath):
def getmtime(path):
def call_process(args, bufsize=0, stdin=None, stdout=None, stderr=None, cwd=None, env=None, input_data=None):
def create_multiprocess_manager():
def _worker_init(worker_init, worker_init_args):
def run_multiprocess_workers(worker, workload, worker_init=None, worker_init_args=(), jobs=None, timeout=None):
class NamedTuple(object):
class UserError(RuntimeError):
import os
import sys
import re
import shutil
from .workspace import get_workspace_location, get_workspace_state, find_catkin_packages, resolve_this
from .config import Config
from .cache import Cache
from .resolver import find_dependees, resolve_system_depends
from .ui import TableView, msg, warning, error, fatal, escape, \
                show_conflicts, show_missing_system_depends, \
                textify, LARROW, RARROW, FF_LARROW, FF_RARROW
from .util import iteritems, path_has_prefix, has_package_path, call_process, PIPE, \
                create_multiprocess_manager, run_multiprocess_workers
from pygit2 import clone_repository, Repository, \
                RemoteCallbacks, KeypairFromAgent, UserPass, \
                GIT_CREDTYPE_SSH_KEY, GIT_CREDTYPE_USERPASS_PLAINTEXT, \
                GIT_STATUS_CURRENT, GIT_STATUS_IGNORED, GIT_BRANCH_LOCAL, \
                GIT_BRANCH_REMOTE, GitError, \
                features as pygit2_features, GIT_FEATURE_HTTPS, GIT_FEATURE_SSH
from functools import partial
    from urlparse import urlsplit
    from urllib.parse import urlsplit
                        status.append("@!@{cf}needs pull -L")
                    else:
                        if not is_ancestor(repo, master_branch, head_branch):
                            status.append("@!@{yf}needs merge --from-master")
                        if not is_up_to_date(repo, head_branch, master_branch):
                            status.append("@!@{yf}needs merge --to-master")
            if master_branch is not None and master_remote_branch is not None and (tracking_branch is None or tracking_branch.name != master_remote_branch.name):
                if need_push(repo, master_branch):
                    status.append("@!@{yf}%s needs push" % master_branch.shorthand)
                elif need_pull(repo, master_branch):
                    status.append("@!@{cf}%s needs pull" % master_branch.shorthand)
                elif not is_up_to_date(repo, master_branch):
                    status.append("@!@{yf}%s needs merge" % master_branch.shorthand)
        return status

    def create_local_status(repo, upstream_status, is_dirty):
        status = []
        if repo.head_is_detached:
            status.append("@!@{rf}detached HEAD")
            return status
        if has_pending_merge(repo):
            if repo.index.conflicts:
                status.append("@!@{rf}merge conflicts")
            else:
                status.append("@!@{yf}merged, needs commit")
            return status
        if is_dirty:
            status.append("@!@{yf}needs commit")
        status += upstream_status
        if not status:
            if not show_up_to_date:
                return None
            status.append("@!@{gf}up-to-date")
        return status

    table = TableView("Package", "Path", "Status")

    found_packages = set()
    for project in projects:
        repo = Repository(os.path.join(srcdir, project.workspace_path, ".git"))
        dirty_files = [a for a, b in iteritems(repo.status()) if b != GIT_STATUS_IGNORED and b != GIT_STATUS_CURRENT]
        head_branch = get_head_branch(repo)
        tracking_branch = head_branch.upstream if head_branch else None
        master_remote = get_origin(repo, project)
        if master_remote is not None:
            master_remote_branch = repo.lookup_branch("%s/%s" % (master_remote.name, project.master_branch), GIT_BRANCH_REMOTE)
            master_branch = None
            if master_remote_branch is not None:
                for name in repo.listall_branches(GIT_BRANCH_LOCAL):
                    b = repo.lookup_branch(name, GIT_BRANCH_LOCAL)
                    if b.upstream and b.upstream.branch_name == master_remote_branch.branch_name:
                        master_branch = b
                        break
        else:
            master_remote_branch = None
            master_branch = None
        ws_packages = find_catkin_packages(srcdir, project.workspace_path, cache=cache)
        found_packages |= set(ws_packages.keys())
        upstream_status = create_upstream_status(repo, head_branch, master_branch, master_remote_branch, tracking_branch)
        for name, pkg_list in iteritems(ws_packages):
            if name not in packages:
                continue
            for pkg in pkg_list:
                is_dirty = False
                local_path = os.path.relpath(pkg.workspace_path, project.workspace_path)
                if dirty_files and local_path == ".":
                    is_dirty = True
                else:
                    for fpath in dirty_files:
                        if path_has_prefix(fpath, local_path):
                            is_dirty = True
                            break
                status = create_local_status(repo, upstream_status, is_dirty)
                if status is not None:
                    head, tail = os.path.split(pkg.workspace_path)
                    pkg_path = escape(head + "/" if tail == name else pkg.workspace_path)
                    table.add_row(escape(name), pkg_path, status)

    for path in other_git:
        repo = Repository(os.path.join(srcdir, path, ".git"))
        dirty_files = [a for a, b in iteritems(repo.status()) if b != GIT_STATUS_IGNORED and b != GIT_STATUS_CURRENT]
        head_branch = get_head_branch(repo)
        tracking_branch = head_branch.upstream if head_branch else None
        ws_packages = find_catkin_packages(srcdir, path, cache=cache)
        found_packages |= set(ws_packages.keys())
        upstream_status = create_upstream_status(repo, head_branch, None, None, tracking_branch)
        for name, pkg_list in iteritems(ws_packages):
            if name not in packages:
                continue
            for pkg in pkg_list:
                is_dirty = False
                local_path = os.path.relpath(pkg.workspace_path, path)
                if dirty_files and local_path == ".":
                    is_dirty = True
                else:
                    for fpath in dirty_files:
                        if path_has_prefix(fpath, local_path):
                            is_dirty = True
                            break
                status = create_local_status(repo, upstream_status, is_dirty)
                if status is not None:
                    head, tail = os.path.split(pkg.workspace_path)
                    pkg_path = escape(head + "/" if tail == name else pkg.workspace_path)
                    table.add_row(escape(name), pkg_path, status)

    missing = set(packages) - found_packages
    for name in missing:
        path_list = []
        status = "no git"
        if name in ws_state.ws_packages:
            for pkg in ws_state.ws_packages[name]:
                if not os.path.isdir(os.path.join(srcdir, pkg.workspace_path)):
                    status = "@{rf}deleted"
                head, tail = os.path.split(pkg.workspace_path)
                path_list.append(escape(head + "/" if tail == name else pkg.workspace_path))
        table.add_row(escape(name), path_list, status)
    if table.empty():
        if found_packages:
            msg("Everything is @!@{gf}up-to-date@|.\n")
        else:

            warning("no Git repositories\n")