source.py: Split up staging functions into separate codepaths.

Currently we pass a file path string or Directory object to these methods
depending on whether BST_STAGE_VIRTUAL_DIRECTORY is set, which is not very
pretty for plugin developers especially if they are using type annotations
and need to handle `Union[str, Directory]` in these methods.

Instead, created Source.stage_directory() and Source.init_workspace_directory()
to handle the Directory variants of these methods separately.

Also updated `local` and `workspace` plugins to use the new methods.
diff --git a/src/buildstream/plugins/sources/local.py b/src/buildstream/plugins/sources/local.py
index 944324d..54e7679 100644
--- a/src/buildstream/plugins/sources/local.py
+++ b/src/buildstream/plugins/sources/local.py
@@ -90,7 +90,7 @@
         # Nothing to do here for a local source
         pass  # pragma: nocover
 
-    def stage(self, directory):
+    def stage_directory(self, directory):
         #
         # We've already prepared the CAS while resolving the cache key which
         # will happen before staging.
@@ -102,7 +102,7 @@
         with self._cache_directory(digest=self.__digest) as cached_directory:
             directory.import_files(cached_directory)
 
-    def init_workspace(self, directory):
+    def init_workspace_directory(self, directory):
         #
         # FIXME: We should be able to stage the workspace from the content
         #        cached in CAS instead of reimporting from the filesystem
diff --git a/src/buildstream/plugins/sources/workspace.py b/src/buildstream/plugins/sources/workspace.py
index 97a3a08..9127b7c 100644
--- a/src/buildstream/plugins/sources/workspace.py
+++ b/src/buildstream/plugins/sources/workspace.py
@@ -92,13 +92,13 @@
     # init_workspace()
     #
     # Raises AssertionError: existing workspaces should not be reinitialized
-    def init_workspace(self, directory: str) -> None:
+    def init_workspace_directory(self, directory: Directory) -> None:
         raise AssertionError("Attempting to re-open an existing workspace")
 
     def fetch(self, *, previous_sources_dir=None) -> None:  # pylint: disable=arguments-differ
         pass  # pragma: nocover
 
-    def stage(self, directory):
+    def stage_directory(self, directory):
         #
         # We've already prepared the CAS while resolving the cache key which
         # will happen before staging.
diff --git a/src/buildstream/source.py b/src/buildstream/source.py
index b89aa1a..483c62e 100644
--- a/src/buildstream/source.py
+++ b/src/buildstream/source.py
@@ -78,16 +78,16 @@
 
   Fetch the actual payload for the currently set ref
 
-* :func:`Source.stage() <buildstream.source.Source.stage>`
+* :func:`Source.stage() <buildstream.source.Source.stage>` / :func:`Source.stage_directory() <buildstream.source.Source.stage_directory>`
 
   Stage the sources for a given ref at a specified location
 
-* :func:`Source.init_workspace() <buildstream.source.Source.init_workspace>`
+* :func:`Source.init_workspace() <buildstream.source.Source.init_workspace>` / :func:`Source.init_workspace_workspace() <buildstream.source.Source.init_workspace_directory>`
 
-  Stage sources in a local directory for use as a workspace.
+  Stage sources for use as a workspace.
 
-  **Optional**: If left unimplemented, this will default to calling
-  :func:`Source.stage() <buildstream.source.Source.stage>`
+  **Optional**: If left unimplemented, these will default to calling
+  :func:`Source.stage() <buildstream.source.Source.stage>` / :func:`Source.stage_directory() <buildstream.source.Source.stage_directory>`
 
 * :func:`Source.get_source_fetchers() <buildstream.source.Source.get_source_fetchers>`
 
@@ -163,7 +163,7 @@
 from . import _yaml, utils
 from .node import MappingNode
 from .plugin import Plugin
-from .types import SourceRef, Union, CoreWarnings
+from .types import SourceRef, CoreWarnings
 from ._exceptions import BstError, ImplError, PluginError
 from .exceptions import ErrorDomain
 from ._loader.metasource import MetaSource
@@ -307,8 +307,10 @@
     BST_STAGE_VIRTUAL_DIRECTORY = False
     """Whether we can stage this source directly to a virtual directory
 
-    When set to true, virtual directories can be passed to the source to stage
-    to.
+    When set to True, :func:`Source.stage_directory() <buildstream.source.Source.stage_directory>`
+    and :func:`Source.init_workspace_directory() <buildstream.source.Source.init_workspace_directory>`
+    will be called in place of :func:`Source.stage() <buildstream.source.Source.stage>` and
+    :func:`Source.init_workspace() <buildstream.source.Source.init_workspace>` respectively.
     """
 
     def __init__(
@@ -485,7 +487,7 @@
         """
         raise ImplError("Source plugin '{}' does not implement fetch()".format(self.get_kind()))
 
-    def stage(self, directory: Union[str, Directory]) -> None:
+    def stage(self, directory: str) -> None:
         """Stage the sources to a directory
 
         Args:
@@ -502,11 +504,34 @@
         """
         raise ImplError("Source plugin '{}' does not implement stage()".format(self.get_kind()))
 
-    def init_workspace(self, directory: str) -> None:
-        """Initialises a new workspace
+    def stage_directory(self, directory: Directory) -> None:
+        """Stage the sources to a directory
 
         Args:
-           directory: Path of the workspace to init
+           directory: :class:`.Directory` object to stage the source into
+
+        Raises:
+           :class:`.SourceError`
+
+        Implementors should assume that *directory* represents an existing
+        directory root into which the source content can be populated.
+
+        Implementors should raise :class:`.SourceError` when encountering
+        some system error.
+
+        .. note::
+
+           This will be called *instead* of :func:`Source.stage() <buildstream.source.Source.stage>`
+           in the case that :attr:`~buildstream.source.Source.BST_STAGE_VIRTUAL_DIRECTORY` is set
+           for this plugin.
+        """
+        raise ImplError("Source plugin '{}' does not implement stage_directory()".format(self.get_kind()))
+
+    def init_workspace(self, directory: str) -> None:
+        """Stage sources for use as a workspace.
+
+        Args:
+           directory: Path of the workspace to initialize.
 
         Raises:
            :class:`.SourceError`
@@ -522,6 +547,32 @@
         """
         self.stage(directory)
 
+    def init_workspace_directory(self, directory: Directory) -> None:
+        """Stage sources for use as a workspace.
+
+        Args:
+           directory: :class:`.Directory` object of the workspace to initialize.
+
+        Raises:
+           :class:`.SourceError`
+
+        Default implementation is to call
+        :func:`Source.stage_directory() <buildstream.source.Source.stage_directory>`.
+
+        Implementors overriding this method should assume that *directory*
+        already exists.
+
+        Implementors should raise :class:`.SourceError` when encountering
+        some system error.
+
+        .. note::
+
+           This will be called *instead* of
+           :func:`Source.init_workspace() <buildstream.source.Source.init_workspace>` in the case that
+           :attr:`~buildstream.source.Source.BST_STAGE_VIRTUAL_DIRECTORY` is set for this plugin.
+        """
+        self.stage_directory(directory)
+
     def get_source_fetchers(self) -> Iterable[SourceFetcher]:
         """Get the objects that are used for fetching
 
@@ -823,7 +874,10 @@
     #
     def _stage(self, directory):
         self.validate_cache()
-        self.stage(directory)
+        if isinstance(directory, Directory):
+            self.stage_directory(directory)
+        else:
+            self.stage(directory)
 
     # Wrapper for init_workspace()
     def _init_workspace(self, directory):
@@ -831,7 +885,10 @@
             directory = FileBasedDirectory(external_directory=directory)
 
         self.validate_cache()
-        self.init_workspace(directory)
+        if isinstance(directory, Directory):
+            self.init_workspace_directory(directory)
+        else:
+            self.init_workspace(directory)
 
     # _get_unique_key():
     #