/// <summary>
        /// Get a teammate's shared project from the specified source.
        /// </summary>
        /// <param name="parent">Window that will be parent of progress window</param>
        /// <param name="projectFilter">Function taking a directory path and telling whether it contains the right sort of repo</param>
        /// <param name="hubQuery">String on which to build a URL query to ChorusHub to accomplish the purpose of 'projectFilter'
        /// in the ChorusHub environment</param>
        /// <example>FLExBridge sends "fileExtension=.lift|._custom_properties" to get both LIFT and FLExBridge repos, but not Bloom ones,
        /// for instance. The server looks in the project's .hg/store/data folder for a file ending in .lift.i or ._custom_properties.i</example>
        /// <param name="baseProjectDirForNewClone">The base folder for the new clone, if created.</param>
        /// <param name="baseProjectDirInWhichToSearchForRepositories">The directory which contains projects we already have, and where the result should go</param>
        /// <param name="lowerLevelRepoPath">Optionally specifies another place to look for existing repos: look in this subfolder of each folder in baseProjectDirInWhichToSearchForRepositories.
        /// This is used in FLEx (passing "OtherRepositories") so existing LIFT repos linked to FW projects can be found. Pass null if not used.</param>
        /// <param name="preferredClonedFolderName"></param>
        /// <param name="howToSendReceiveMessageText">This string is appended to the message we build when we have received a repo and can't keep it, because
        /// it has the same hash as an existing project. We think it is likely the user actually intended to Send/Receive that project rather than obtaining
        /// a duplicate. This message thus typically tells him how to do so, in the particular client program. May also be empty.</param>
        /// <returns>
        /// A CloneResult that provides the clone results (e.g., success or failure) and the actual clone location (null if not created).
        /// </returns>
        public CloneResult GetSharedProjectUsing(Form parent, string baseProjectDirForNewClone, string preferredClonedFolderName,
                                                 Func <string, bool> projectFilter, string hubQuery, string baseProjectDirInWhichToSearchForRepositories, string lowerLevelRepoPath,
                                                 string howToSendReceiveMessageText)
        {
            Guard.AgainstNull(parent, "parent");
            Guard.AgainstNullOrEmptyString(baseProjectDirForNewClone, "baseProjectDirForNewClone");
            Guard.AgainstNullOrEmptyString(baseProjectDirInWhichToSearchForRepositories, "baseProjectDirInWhichToSearchForRepositories");
            if (preferredClonedFolderName == string.Empty)
            {
                preferredClonedFolderName = null;
            }

            Dictionary <string, string> existingRepositories;

            try
            {
                existingRepositories = ExtantRepoIdentifiers(baseProjectDirInWhichToSearchForRepositories, lowerLevelRepoPath);
            }
            catch (ApplicationException e)
            {
                // FLEx issue LT-14301: one reason we may throw is that we can't get the identifier of some project because we don't have
                // sufficient permissions.

                // We think this will be very rare...try to get an automatic notification if it happens.
                UsageReporter.SendEvent("UnusualProblems", "Chorus", "ExtantRepoIdentifiersFailed", null, 0);

                MessageBox.Show(
                    string.Format(LocalizationManager.GetString("Messages.CantGetInfo",
                                                                "You can't get a project from a colleague at present, because some required information about the projects you already have is unavailable. "
                                                                + "This may be because you don't have permission to access a file in one of the projects in {0}.\n\n"
                                                                + "You will probably need technical support to resolve this problem. The following information may be helpful to tech support:") + "\n\n{1}",
                                  baseProjectDirInWhichToSearchForRepositories, e.Message),
                    LocalizationManager.GetString("Messages.CantGetProject", "Cannot get project"));
                return(new CloneResult(null, CloneStatus.NotCreated));
            }
            var existingProjectNames = new HashSet <string>(from dir in Directory.GetDirectories(baseProjectDirInWhichToSearchForRepositories) select Path.GetFileName(dir));

            // "existingRepositoryIdentifiers" is currently not used, but the expectation is that the various models/views could use it how they see fit.
            // "Seeing fit' may mean to warn the user they already have some repository, or as a filter to not show ones that already exist.
            // What to do with the list of extant repos is left up to a view+model pair.

            // Select basic source type.
            using (var getSharedProjectDlg = new GetSharedProjectDlg())
            {
                getSharedProjectDlg.InitFromModel(this);
                getSharedProjectDlg.ShowDialog(parent);
                if (getSharedProjectDlg.DialogResult != DialogResult.OK)
                {
                    return(new CloneResult(null, CloneStatus.NotCreated));
                }
            }

            // Make clone from some source.
            string actualCloneLocation = null;
            var    cloneStatus         = CloneStatus.NotCreated;

            switch (RepositorySource)
            {
            case ExtantRepoSource.Internet:
                var cloneFromInternetModel = new GetCloneFromInternetModel(baseProjectDirForNewClone)
                {
                    LocalFolderName = preferredClonedFolderName
                };
                using (var cloneFromInternetDialog = new GetCloneFromInternetDialog(cloneFromInternetModel))
                {
                    switch (cloneFromInternetDialog.ShowDialog(parent))
                    {
                    default:
                        cloneStatus = CloneStatus.NotCreated;
                        break;

                    case DialogResult.Cancel:
                        cloneStatus = CloneStatus.Cancelled;
                        break;

                    case DialogResult.OK:
                        actualCloneLocation = cloneFromInternetDialog.PathToNewlyClonedFolder;
                        cloneStatus         = CloneStatus.Created;
                        break;
                    }
                }
                break;

            case ExtantRepoSource.ChorusHub:
                var getCloneFromChorusHubModel = new GetCloneFromChorusHubModel(baseProjectDirForNewClone)
                {
                    ProjectFilter    = hubQuery,
                    ExistingProjects = existingProjectNames,
                    ExistingRepositoryIdentifiers = existingRepositories
                };

                using (var getCloneFromChorusHubDialog = new GetCloneFromChorusHubDialog(getCloneFromChorusHubModel))
                {
                    switch (getCloneFromChorusHubDialog.ShowDialog(parent))
                    {
                    default:
                        cloneStatus = CloneStatus.NotCreated;
                        break;

                    case DialogResult.Cancel:
                        cloneStatus = CloneStatus.Cancelled;
                        break;

                    case DialogResult.OK:
                        if (getCloneFromChorusHubModel.CloneSucceeded)
                        {
                            actualCloneLocation = getCloneFromChorusHubDialog.PathToNewlyClonedFolder;
                            cloneStatus         = CloneStatus.Created;
                        }
                        else
                        {
                            cloneStatus = CloneStatus.NotCreated;
                        }
                        break;
                    }
                }
                break;

            case ExtantRepoSource.Usb:
                using (var cloneFromUsbDialog = new GetCloneFromUsbDialog(baseProjectDirForNewClone))
                {
                    cloneFromUsbDialog.Model.ProjectFilter    = projectFilter ?? DefaultProjectFilter;
                    cloneFromUsbDialog.Model.ReposInUse       = existingRepositories;
                    cloneFromUsbDialog.Model.ExistingProjects = existingProjectNames;
                    switch (cloneFromUsbDialog.ShowDialog(parent))
                    {
                    default:
                        cloneStatus = CloneStatus.NotCreated;
                        break;

                    case DialogResult.Cancel:
                        cloneStatus = CloneStatus.Cancelled;
                        break;

                    case DialogResult.OK:
                        actualCloneLocation = cloneFromUsbDialog.PathToNewlyClonedFolder;
                        cloneStatus         = CloneStatus.Created;
                        break;
                    }
                }
                break;
            }
            // Warn the user if they already have this by another name.
            // Not currently needed for USB, since those have already been checked.
            if (RepositorySource != ExtantRepoSource.Usb && cloneStatus == CloneStatus.Created)
            {
                var    repo = new HgRepository(actualCloneLocation, new NullProgress());
                string projectWithExistingRepo;
                if (repo.Identifier != null && existingRepositories.TryGetValue(repo.Identifier, out projectWithExistingRepo))
                {
                    using (var warningDlg = new DuplicateProjectWarningDialog())
                        warningDlg.Run(projectWithExistingRepo, howToSendReceiveMessageText);
                    Directory.Delete(actualCloneLocation, true);
                    actualCloneLocation = null;
                    cloneStatus         = CloneStatus.Cancelled;
                }
            }
            return(new CloneResult(actualCloneLocation, cloneStatus));
        }
        /// <summary>
        /// Get a teammate's shared project from the specified source.
        /// </summary>
        /// <param name="parent">Window that will be parent of progress window</param>
        /// <param name="projectFilter">Function taking a directory path and telling whether it contains the right sort of repo</param>
        /// <param name="hubQuery">String on which to build a URL query to ChorusHub to accomplish the purpose of 'projectFilter'
        /// in the ChorusHub environment</param>
        /// <example>FLExBridge sends "fileExtension=.lift|._custom_properties" to get both LIFT and FLExBridge repos, but not Bloom ones,
        /// for instance. The server looks in the project's .hg/store/data folder for a file ending in .lift.i or ._custom_properties.i</example>
        /// <param name="baseProjectDirForNewClone">The base folder for the new clone, if created.</param>
        /// <param name="baseProjectDirInWhichToSearchForRepositories">The directory which contains projects we already have, and where the result should go</param>
        /// <param name="lowerLevelRepoPath">Optionally specifies another place to look for existing repos: look in this subfolder of each folder in baseProjectDirInWhichToSearchForRepositories.
        /// This is used in FLEx (passing "OtherRepositories") so existing LIFT repos linked to FW projects can be found. Pass null if not used.</param>
        /// <param name="preferredClonedFolderName"></param>
        /// <param name="howToSendReceiveMessageText">This string is appended to the message we build when we have received a repo and can't keep it, because
        /// it has the same hash as an existing project. We think it is likely the user actually intended to Send/Receive that project rather than obtaining
        /// a duplicate. This message thus typically tells him how to do so, in the particular client program. May also be empty.</param>
        /// <returns>
        /// A CloneResult that provides the clone results (e.g., success or failure) and the actual clone location (null if not created).
        /// </returns>
        public CloneResult GetSharedProjectUsing(Form parent, string baseProjectDirForNewClone, string preferredClonedFolderName,
			Func<string, bool> projectFilter, string hubQuery, string baseProjectDirInWhichToSearchForRepositories, string lowerLevelRepoPath,
			string howToSendReceiveMessageText)
        {
            Guard.AgainstNull(parent, "parent");
            Guard.AgainstNullOrEmptyString(baseProjectDirForNewClone, "baseProjectDirForNewClone");
            Guard.AgainstNullOrEmptyString(baseProjectDirInWhichToSearchForRepositories, "baseProjectDirInWhichToSearchForRepositories");
            if (preferredClonedFolderName == string.Empty)
                preferredClonedFolderName = null;

            Dictionary<string, string> existingRepositories;
            try
            {
                existingRepositories = ExtantRepoIdentifiers(baseProjectDirInWhichToSearchForRepositories, lowerLevelRepoPath);
            }
            catch (ApplicationException e)
            {
                // FLEx issue LT-14301: one reason we may throw is that we can't get the identifier of some project because we don't have
                // sufficient permissions.

                // We think this will be very rare...try to get an automatic notification if it happens.
                UsageReporter.SendEvent("UnusualProblems", "Chorus", "ExtantRepoIdentifiersFailed", null, 0);

                MessageBox.Show(
                    string.Format(LocalizationManager.GetString("Messages.CantGetInfo",
                        "You can't get a project from a colleague at present, because some required information about the projects you already have is unavailable. "
                + "This may be because you don't have permission to access a file in one of the projects in {0}.\n\n"
                + "You will probably need technical support to resolve this problem. The following information may be helpful to tech support:") + "\n\n{1}",
                        baseProjectDirInWhichToSearchForRepositories, e.Message),
                        LocalizationManager.GetString("Messages.CantGetProject", "Cannot get project"));
                return new CloneResult(null, CloneStatus.NotCreated);
            }
            var existingProjectNames = new HashSet<string>(from dir in Directory.GetDirectories(baseProjectDirInWhichToSearchForRepositories) select Path.GetFileName(dir));

            // "existingRepositoryIdentifiers" is currently not used, but the expectation is that the various models/views could use it how they see fit.
            // "Seeing fit' may mean to warn the user they already have some repository, or as a filter to not show ones that already exist.
            // Waht to do with the list of extant repos is left up to a view+model pair.

            // Select basic source type.
            using (var getSharedProjectDlg = new GetSharedProjectDlg())
            {
                getSharedProjectDlg.InitFromModel(this);
                getSharedProjectDlg.ShowDialog(parent);
                if (getSharedProjectDlg.DialogResult != DialogResult.OK)
                {
                    return new CloneResult(null, CloneStatus.NotCreated);
                }
            }

            // Make clone from some source.
            string actualCloneLocation = null;
            var cloneStatus = CloneStatus.NotCreated;
            switch (RepositorySource)
            {
                case ExtantRepoSource.Internet:
                    var cloneFromInternetModel = new GetCloneFromInternetModel(baseProjectDirForNewClone)
                        {
                            LocalFolderName = preferredClonedFolderName
                        };
                    using (var cloneFromInternetDialog = new GetCloneFromInternetDialog(cloneFromInternetModel))
                    {
                        switch (cloneFromInternetDialog.ShowDialog(parent))
                        {
                            default:
                                cloneStatus = CloneStatus.NotCreated;
                                break;
                            case DialogResult.Cancel:
                                cloneStatus = CloneStatus.Cancelled;
                                break;
                            case DialogResult.OK:
                                actualCloneLocation = cloneFromInternetDialog.PathToNewlyClonedFolder;
                                cloneStatus = CloneStatus.Created;
                                break;
                        }
                    }
                    break;

                case ExtantRepoSource.ChorusHub:
                    var getCloneFromChorusHubModel = new GetCloneFromChorusHubModel(baseProjectDirForNewClone)
                    {
                        ProjectFilter = hubQuery,
                        ExistingProjects = existingProjectNames,
                        ExistingRepositoryIdentifiers = existingRepositories
                    };

                    using (var getCloneFromChorusHubDialog = new GetCloneFromChorusHubDialog(getCloneFromChorusHubModel))
                    {
                        switch (getCloneFromChorusHubDialog.ShowDialog(parent))
                        {
                            default:
                                cloneStatus = CloneStatus.NotCreated;
                                break;
                            case DialogResult.Cancel:
                                cloneStatus = CloneStatus.Cancelled;
                                break;
                            case DialogResult.OK:
                                if (getCloneFromChorusHubModel.CloneSucceeded)
                                {
                                    actualCloneLocation = getCloneFromChorusHubDialog.PathToNewlyClonedFolder;
                                    cloneStatus = CloneStatus.Created;
                                }
                                else
                                {
                                    cloneStatus = CloneStatus.NotCreated;
                                }
                                break;
                        }
                    }
                    break;

                case ExtantRepoSource.Usb:
                    using (var cloneFromUsbDialog = new GetCloneFromUsbDialog(baseProjectDirForNewClone))
                    {
                        cloneFromUsbDialog.Model.ProjectFilter = projectFilter ?? DefaultProjectFilter;
                        cloneFromUsbDialog.Model.ReposInUse = existingRepositories;
                        cloneFromUsbDialog.Model.ExistingProjects = existingProjectNames;
                        switch (cloneFromUsbDialog.ShowDialog(parent))
                        {
                            default:
                                cloneStatus = CloneStatus.NotCreated;
                                break;
                            case DialogResult.Cancel:
                                cloneStatus = CloneStatus.Cancelled;
                                break;
                            case DialogResult.OK:
                                actualCloneLocation = cloneFromUsbDialog.PathToNewlyClonedFolder;
                                cloneStatus = CloneStatus.Created;
                                break;
                        }
                    }
                    break;

            }
            // Warn the user if they already have this by another name.
            // Not currently needed for USB, since those have already been checked.
            if (RepositorySource != ExtantRepoSource.Usb && cloneStatus == CloneStatus.Created)
            {
                var repo = new HgRepository(actualCloneLocation, new NullProgress());
                string projectWithExistingRepo;
                if (repo.Identifier != null && existingRepositories.TryGetValue(repo.Identifier, out projectWithExistingRepo))
                {
                    using (var warningDlg = new DuplicateProjectWarningDialog())
                        warningDlg.Run(projectWithExistingRepo, howToSendReceiveMessageText);
                    Directory.Delete(actualCloneLocation, true);
                    actualCloneLocation = null;
                    cloneStatus = CloneStatus.Cancelled;
                }

            }
            return new CloneResult(actualCloneLocation, cloneStatus);
        }