Viewing File: /usr/local/cpanel/base/frontend/jupiter/version_control/views/manageRepositoriesController.js

/*
# version_control/views/manageRepositoriesController.js      Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define, PAGE */

define(
    [
        "angular",
        "lodash",
        "cjt/util/locale",
        "uiBootstrap",
        "app/services/versionControlService",
        "app/services/sseAPIService",
        "cjt/services/alertService",
        "cjt/directives/alert",
        "cjt/directives/alertList",
        "cjt/directives/actionButtonDirective",
        "jquery-chosen",
        "angular-chosen",
        "cjt/decorators/angularChosenDecorator",
    ],
    function(angular, _, LOCALE) {
        "use strict";

        var app = angular.module("cpanel.versionControl");
        app.value("PAGE", PAGE);

        var controller = app.controller(
            "ManageRepositoriesController",
            ["$scope", "$window", "$location", "$timeout", "versionControlService", "sseAPIService", "PAGE", "$routeParams", "alertService", "sshKeyVerification", "$q",
                function($scope, $window, $location, $timeout, versionControlService, sseAPIService, PAGE, $routeParams, alertService, sshKeyVerification, $q) {

                    var repository = this;

                    // RTL check for chosen
                    repository.isRTL = false;
                    var html = document.querySelector("html");
                    if (html) {
                        repository.isRTL = html.getAttribute("dir") === "rtl";
                    }

                    // Page defaults
                    repository.isLoading = true;

                    repository.deployInProgress = false;
                    repository.deployState = "";
                    repository.deployedTaskInformation = null;
                    repository.deployCalloutType = "info";

                    // SSE events and config
                    var deploySSEURL = "";
                    var sseObj;
                    var events = [ "log_update", "task_complete", "task_failed" ];
                    var config = { json: true };

                    var tabs = [
                        "basic-info",
                        "deploy"
                    ];

                    var tabToSelect = 0;

                    // Get the variables from the URL
                    var requestedRepoPath = decodeURIComponent($routeParams.repoPath);
                    var tabName = decodeURIComponent($routeParams.tabname);

                    selectActiveTab(tabName);

                    /**
                     * Selects Active Tab
                     * @method selectActiveTab
                     * @param {String} tabName Tab Name
                     */
                    function selectActiveTab(tabName) {

                        // Selecting tab based on route parameter
                        if (tabName) {
                            tabToSelect = tabs.indexOf(tabName);
                            if ( tabToSelect !== -1) {
                                $scope.activeTabIndex = tabToSelect;
                            } else {
                                $location.path("/list/");
                            }
                        } else {
                            $location.path("/list/");
                        }
                    }

                    retrieveRepositoryInfo(requestedRepoPath);

                    /**
                    * Changes active tab
                    *
                    * @method changeActiveTab
                    * @param {String} name name of the tab.
                    */
                    $scope.changeActiveTab = function(name) {
                        var url = $location.url();
                        var lastPart = url.split( "/" ).pop().toLowerCase();

                        if (name) {
                            $scope.activeTabIndex = tabs.indexOf(name);

                            // lastpart other than name
                            if (lastPart !== name) {
                                $location.path("/manage/" + encodeURIComponent(requestedRepoPath) + "/" + name);
                            }
                        }
                    };

                    /**
                    * Checks to see if the user came from the VersionControl List View
                    *
                    * @method retrieveRepositoryInfo
                    * @param {String} requestedRepoPath Represents the path of the repository to be loaded on the page.
                    */
                    function retrieveRepositoryInfo(requestedRepoPath) {

                        var repoInfo;
                        return versionControlService.getRepositoryInformation(requestedRepoPath, "name,tasks,clone_urls,branch,last_update,source_repository,last_deployment,deployable")
                            .then(function(response) {
                                repoInfo = response;
                                var branchPromise = _retrieveAvailableBranches(requestedRepoPath);

                                /**
                                 * If we fail to retrieve the available branches, we will let the UI
                                 * go ahead and display in its mostly broken state, while performing
                                 * this SSH key check in the background for later use with the Try
                                 * Again button.
                                 */
                                branchPromise.catch(function() {
                                    var sshServer = sshKeyVerification.getHostnameAndPort(response && response.source_repository && response.source_repository.url);
                                    if (sshServer) {
                                        repository.ssh = {};
                                        repository.ssh.hostname = sshServer.hostname;
                                        repository.ssh.port = sshServer.port;
                                        repository.ssh.promise = sshKeyVerification.verify(sshServer.hostname, sshServer.port);
                                    }
                                });

                                return branchPromise;
                            }, function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: _.escape(error),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });
                                $location.path("/list/");

                            })
                            .finally(function() {
                                setFormData(repoInfo);
                                repository.isLoading = false;
                            });
                    }

                    function _retrieveAvailableBranches(requestedRepoPath) {
                        return versionControlService.getRepositoryInformation(requestedRepoPath, "available_branches")
                            .then(function(response) {
                                repository.branchList = response && response.available_branches || [];
                            }, function(error) {
                                repository.unableToRetrieveAvailableBranches = true;
                                return $q.reject(error);
                            });
                    }

                    /**
                     * We don't want to show an alert if there are issues fetching the branches during the
                     * initial page load, but if it's in response to a user action it's good to have feedback.
                     */
                    function _retrieveAvailableBranchesTryAgain() {
                        return _retrieveAvailableBranches( repository.repoPath ).catch(function() {
                            alertService.add({
                                type: "danger",
                                id: "retrieve-branches-again-error",
                                message: _.escape(LOCALE.maketext("The system cannot update information for the repository at “[_1]” because it cannot access the remote repository.", repository.repoPath)),
                                closeable: true,
                                replace: true,
                                group: "versionControl"
                            });
                        });
                    }

                    /**
                     * Changes the text for the callout that's used when unableToRetrieveAvailableBranches = true.
                     *
                     * @param  {Boolean} hasRemote   True when the repository was cloned from a remote repo.
                     */
                    $scope.$watch("repository.hasRemote", function(hasRemote) {
                        if (hasRemote) {
                            repository._noConnectionText = LOCALE.maketext("The system could not contact the remote repository.");
                            repository._tryAgainTooltipText = LOCALE.maketext("Attempt to contact the remote repository again.");
                        } else {
                            repository._noConnectionText = LOCALE.maketext("The system could not read from the repository.");
                            repository._tryAgainTooltipText = LOCALE.maketext("Attempt to read from the repository again.");
                        }
                    });

                    /**
                     * Try to fetch the list of available_branches again.
                     *
                     * @return {Promise}                    Resolves if it successfully retrieves the available branches.
                     *                                      Rejects otherwise.
                     */
                    repository.tryAgain = function tryAgain() {
                        alertService.removeById("retrieve-branches-again-error", "versionControl");
                        if (!repository.ssh.promise) {
                            return _retrieveAvailableBranchesTryAgain();
                        }

                        return repository.ssh.promise.then(
                            function success() {

                                // SSH host key verification is not the problem, so just try again
                                return _retrieveAvailableBranchesTryAgain();
                            },
                            function failure(data) {
                                repository.ssh.status = data.status;
                                repository.ssh.keys = data.keys;

                                _showKeyVerificationModal();
                            }
                        );
                    };

                    /**
                     * Opens up the modal for SSH key verification.
                     */
                    function _showKeyVerificationModal() {
                        alertService.clear(null, "versionControl");

                        repository.ssh.modal = sshKeyVerification.openModal({
                            hostname: repository.ssh.hostname,
                            port: repository.ssh.port,
                            type: repository.ssh.status,
                            keys: repository.ssh.keys,
                            onAccept: _onAcceptKey,
                        });

                        /**
                         * Handle the case where the modal is dismissed, rather than closed.
                         * Dismissal is when the user clicks the cancel button or if they click
                         * outside of the modal, causing it to disappear.
                         */
                        repository.ssh.modal.result.catch(function() {
                            alertService.add({
                                type: "danger",
                                message: _.escape(LOCALE.maketext("The system [output,strong,cannot] connect to the remote repository if you do not accept the host key for “[output,strong,_1].”", repository.ssh.hostname)),
                                closeable: true,
                                replace: true,
                                group: "versionControl",
                                id: "known-hosts-verification-cancelled",
                            });
                        });

                        return repository.ssh.modal;
                    }

                    function _onAcceptKey(promise) {
                        return promise.then(
                            function success(newStatus) {
                                repository.ssh.status = newStatus;
                                return _retrieveAvailableBranchesTryAgain();
                            },
                            function failure(error) {
                                alertService.add({
                                    type: "danger",
                                    message: _.escape(LOCALE.maketext("The system failed to add the fingerprints from “[_1]” to the [asis,known_hosts] file: [_2]", repository.ssh.hostname, error)),
                                    closeable: true,
                                    replace: true,
                                    group: "versionControl",
                                    id: "known-hosts-verification-failure",
                                });
                            }
                        ).finally(function() {
                            repository.ssh.modal.close();
                            delete repository.ssh.modal;
                        });
                    }

                    /**
                    * Set Manage Form Data
                    *
                    * @method setFormData
                    * @param {object} data Represents the single repository data object.
                    */
                    function setFormData(data) {
                        repository.name = data.name;
                        repository.repoPath = data.repository_root;
                        repository.cloneURL = data.clone_urls.read_write[0];

                        repository.branch = data.branch;
                        repository.checkedoutBranch = data.branch;

                        repository.hasActiveBranch = data.hasActiveBranch;

                        repository.hasHeadInformation = data.hasHeadInformation;
                        repository.lastUpdateSHA = data.lastUpdateSHA;
                        repository.lastUpdateDate = data.lastUpdateDate;
                        repository.commitMessage = data.commitMessage;
                        repository.author = data.author;

                        if (data.available_branches) {
                            repository.branchList = data.available_branches;
                        }

                        repository.hasRemote = data.hasRemote;
                        repository.remoteInformation = data.source_repository;

                        repository.gitWebURL = data.gitWebURL;
                        repository.fileManagerRedirectURL = data.fileManagerRedirectURL;

                        repository.fullRepoPath = repository.repoPath;

                        repository.qaSafeSuffix = data.qaSafeSuffix;

                        repository.deployInProgress = data.deployInProgress;

                        repository.deployable = data.deployable;
                        repository.hasDeploymentInformation = data.hasDeploymentInformation;
                        repository.lastDeployedDate = data.lastDeployedDate;
                        repository.lastDeployedSHA = data.lastDeployedSHA;
                        repository.lastDeployedAuthor = data.lastDeployedAuthor;
                        repository.lastDeployedCommitDate = data.lastDeployedCommitDate;
                        repository.lastDeployedCommitMessage = data.lastDeployedCommitMessage;

                        repository.changesAvailableToDeploy = data.lastDeployedSHA !== data.lastUpdateSHA;

                        repository.deployTasks = getDeployTasks(data.tasks);

                        if (typeof sseObj === "undefined" && repository.deployInProgress) {
                            initializeSSE();
                        }
                    }

                    function getDeployTasks(tasks) {
                        var deployTasks =  _.map(tasks, function(task) {
                            if (task.action === "deploy") {
                                var timestampInfo = getDeployTimestamp(task.args.log_file);
                                return {
                                    task_id: task.task_id,
                                    log_file: task.args.log_file,
                                    sse_url: task.sse_url,
                                    timeStamp: timestampInfo,
                                    humanReadableDate: LOCALE.local_datetime(timestampInfo, "datetime_format_medium")
                                };
                            }
                        });

                        return _.sortBy(deployTasks, [function(o) {
                            return o.log_file;
                        }]);
                    }

                    /**
                     * @method getQueuedTaskString
                     * @param {Number} taskCount TaskCount
                     * @returns {String} Additional tasks display string
                     */
                    function getQueuedTaskString(taskCount) {
                        return LOCALE.maketext("[quant,_1,additional task,additional tasks] queued", taskCount);
                    }

                    /**
                     * Gets Deployment timestamp
                     * @method getDeployTimestamp
                     * @param {String} logFilePath LogFile path
                     * @returns {String} deployment timestamp
                     */
                    function getDeployTimestamp(logFilePath) {
                        var timeStamp;
                        if (logFilePath) {
                            var logFileName = logFilePath.split("/").pop();
                            timeStamp = logFileName.match(/\d+(\.\d+)/g);
                        }
                        return timeStamp[0];
                    }

                    /**
                     * Update Repository
                     * @method updateRepository
                     * @return {Promise} Returns a promise from the VersionControlService.updateRepository method for success/error handling when the user requests to update a repository.
                     */
                    repository.updateRepository = function() {

                        var branch = repository.branch === repository.checkedoutBranch ? "" : repository.branch;

                        return versionControlService.updateRepository(
                            repository.repoPath,
                            repository.name,
                            branch
                        ).then(function(response) {

                            alertService.add({
                                type: "success",
                                message: _.escape(LOCALE.maketext("The system successfully updated the “[_1]” repository.", repository.name)),
                                closeable: true,
                                replace: true,
                                autoClose: 10000,
                                group: "versionControl"
                            });

                            setFormData(response.data);

                        }, function(error) {
                            alertService.add({
                                type: "danger",
                                message: _.escape(error),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    };

                    /**
                     * Pull from remote repository
                     * @method pullFromRemote
                     * @return {Promise} Returns a promise from the VersionControlService.updateRepository method for success/error handling when the user requests to pull from remote repository.
                     */
                    repository.pullFromRemote = function() {

                        return versionControlService.updateFromRemote(
                            repository.repoPath,
                            repository.branch
                        ).then(function(response) {

                            var data = response.data;

                            if (repository.lastUpdateSHA === data.lastUpdateSHA) {
                                alertService.add({
                                    type: "info",
                                    message: _.escape(LOCALE.maketext("The “[_1]” repository is up-to-date.", repository.name)),
                                    closeable: true,
                                    replace: true,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });
                            } else {
                                alertService.add({
                                    type: "success",
                                    message: _.escape(LOCALE.maketext("The system successfully updated the “[_1]” repository.", repository.name)),
                                    closeable: true,
                                    replace: true,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });

                                repository.hasHeadInformation = data.hasHeadInformation;
                                repository.lastUpdateSHA = data.lastUpdateSHA;
                                repository.lastUpdateDate = data.lastUpdateDate;
                                repository.commitMessage = data.commitMessage;
                                repository.author = data.author;

                                repository.newCommits = true;

                                $timeout( function() {
                                    repository.newCommits = false;
                                }, 10000 );
                            }
                        }, function(error) {
                            alertService.add({
                                type: "danger",
                                message: _.escape(error),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    };

                    /**
                     * Reset deployment and sse flags
                     * @method resetSSEState
                     */
                    function resetSSEState() {
                        repository.deployState = "";
                        repository.deployCalloutType = "info";
                        repository.deployedTaskInformation = null;

                        sseObj = null;
                    }

                    /**
                     * Initialize SSE
                     * @method initializeSSE
                     */
                    function initializeSSE() {

                        repository.queuedDeployTasksCount = repository.deployTasks.length - 1;

                        if (repository.queuedDeployTasksCount) {
                            repository.queuedTaskString = getQueuedTaskString(repository.queuedDeployTasksCount);
                        }

                        repository.firstDeployTask = repository.deployTasks[0];
                        deploySSEURL = repository.firstDeployTask.sse_url;

                        repository.deployProgress = LOCALE.maketext("The deployment that you triggered on [_1] is in progress …", repository.firstDeployTask.humanReadableDate);
                        repository.deployComplete = LOCALE.maketext("The deployment that you triggered on [_1] is complete. Updating last deployment information …", repository.firstDeployTask.humanReadableDate);
                        repository.deployQueued =  LOCALE.maketext("The deployment that you triggered on [_1] is queued …", repository.firstDeployTask.humanReadableDate);
                        sseAPIService.initialize();
                    }

                    /**
                     * Handles ready.
                     *
                     * @method
                     * @param {sse:ready} event - ready event.
                     * @listens sse:ready
                     */
                    $scope.$on("sse:ready", function(event) {
                        deploySSEURL = PAGE.securityToken + deploySSEURL;
                        sseObj = sseAPIService.connect(deploySSEURL, events, config);
                    });

                    /**
                     * Handles destroy event.
                     *
                     * @method
                     * @listens $destroy
                     */
                    $scope.$on("$destroy", function() {
                        if (sseObj) {
                            sseAPIService.close(sseObj);
                        }
                    });

                    /**
                     * Handles log_update.
                     *
                     * @method
                     * @param {sse:log_update} event - Task log update event.
                     * @param {String} data - log data
                     * @listens sse:log_update
                     */
                    $scope.$on("sse:log_update", function(event, data) {
                        repository.deployState = "processing";
                        $scope.$apply();
                    });

                    /**
                     * Handles task_complete.
                     *
                     * @method
                     * @param {sse:task_complete} event - Task complete event.
                     * @param {Object} data - Data
                     * @listens sse:task_complete
                     */
                    $scope.$on("sse:task_complete", function(event, data) {
                        var taskData = data;
                        sseAPIService.close(sseObj);
                        repository.deployCalloutType = "success";
                        repository.deployState = "complete";

                        $scope.$apply();

                        $timeout(function() {
                            return versionControlService.getRepositoryInformation(repository.repoPath, "last_deployment,tasks")
                                .then(function(data) {
                                    repository.lastDeployedDate = data.lastDeployedDate;
                                    repository.lastDeployedSHA = data.lastDeployedSHA;
                                    repository.lastDeployedAuthor = data.lastDeployedAuthor;
                                    repository.lastDeployedCommitDate = data.lastDeployedCommitDate;
                                    repository.lastDeployedCommitMessage = data.lastDeployedCommitMessage;

                                    repository.hasDeploymentInformation = true;

                                    repository.changesAvailableToDeploy = data.lastDeployedSHA !== repository.lastUpdateSHA;

                                    repository.deployTasks = getDeployTasks(data.tasks);

                                    resetSSEState();

                                    if (repository.deployTasks && repository.deployTasks.length > 0) {
                                        repository.deployInProgress = true;
                                        initializeSSE();
                                    } else {
                                        repository.deployInProgress = false;
                                    }

                                    repository.newDeployCommit = true;

                                    $timeout( function() {
                                        repository.newDeployCommit = false;
                                    }, 5000 );
                                }, function(error) {

                                    // display error
                                    alertService.add({
                                        type: "danger",
                                        message: _.escape(error.message),
                                        closeable: true,
                                        replace: false,
                                        group: "versionControl"
                                    });
                                });
                        }, 5000);
                    });

                    /**
                     * Handles task_failed.
                     *
                     * @method
                     * @param {sse:task_failed} event - Task failed event.
                     * @param {Object} data - Data
                     * @listens sse:task_failed
                     */
                    $scope.$on("sse:task_failed", function(event, data) {
                        sseAPIService.close(sseObj);
                        var deployedTaskInfo = repository.deployedTaskInformation;
                        var logFileInfo = getLogFileDetails(deployedTaskInfo.log_path);

                        alertService.add({
                            type: "danger",
                            message: LOCALE.maketext("Error occurred while deploying.") +
                                    " " +
                                    LOCALE.maketext("You can view the log file: [output,url,_1,_2,target,_3]", logFileInfo.fileManagerURL, logFileInfo.fileName, "_blank"),
                            closeable: true,
                            replace: false,
                            group: "versionControl"
                        });

                        $scope.$apply();

                        return versionControlService.getRepositoryInformation(repository.repoPath, "tasks")
                            .then(function(data) {
                                repository.deployTasks = getDeployTasks(data.tasks);

                                resetSSEState();

                                if (repository.deployTasks && repository.deployTasks.length > 0) {
                                    repository.deployInProgress = true;
                                    initializeSSE();
                                } else {
                                    repository.deployInProgress = false;
                                }

                            }, function(error) {

                            // display error
                                alertService.add({
                                    type: "danger",
                                    message: _.escape(error.message),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });
                            });
                    });

                    /**
                     * Get log file details
                     * @method getLogFileDetails
                     *
                     * @param {Object} logFilePath logfile path
                     * @return {Object} Log file details
                     */
                    function getLogFileDetails(logFilePath) {
                        var logFileInfo = {};

                        if (logFilePath) {

                            // construct the file manager url for log file
                            var fileName = logFilePath.split( "/" ).pop();
                            var dirPath = PAGE.homeDir + "/.cpanel/logs";
                            var fileManangerURL = PAGE.deprefix + "filemanager/showfile.html?file=" + encodeURIComponent(fileName) + "&dir=" + encodeURIComponent(dirPath);

                            logFileInfo.fileName = fileName;
                            logFileInfo.fileManagerURL =  fileManangerURL;
                        }

                        return logFileInfo;
                    }

                    /**
                     * Deploy repository
                     * @method deployRepository
                     * @return {Promise} Returns a promise from the VersionControlService.deployRepository method for success/error handling when the user requests to deploy their repository.
                     */
                    repository.deployRepository = function() {
                        return versionControlService.deployRepository(
                            repository.repoPath
                        ).then(function(response) {
                            var data = response.data || {};

                            /**
                             * We have to fake the task object, since the task data returned from the
                             * VersionControlDeployment::create and VersionControlDeployment::retrieveAPI calls don't match.
                             */
                            repository.deployTasks = getDeployTasks([
                                {
                                    action: "deploy",
                                    task_id: data.task_id,
                                    sse_url: data.sse_url,
                                    args: {
                                        log_file: data.log_path,
                                    },
                                }
                            ]);

                            if (repository.deployTasks && repository.deployTasks.length > 0) {
                                repository.deployInProgress = true;
                                initializeSSE();
                            } else {
                                repository.deployInProgress = false;
                            }

                        }, function(error) {
                            repository.deployInProgress = false;
                            alertService.add({
                                type: "danger",
                                message: _.escape(error) +
                                         " " +
                                         LOCALE.maketext("For more information, read our [output,url,_1,documentation,target,_2].", "https://go.cpanel.net/GitDeployment", "_blank"),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    };


                    /**
                     * Back to List View
                     * @method backToListView
                     */
                    repository.backToListView = function() {
                        $location.path("/list");
                    };

                    /**
                     * Opens repository in gitWeb
                     * @method redirectToGitWeb
                     * @param {String} gitWebURL gitWebURL for the repository
                     * @param {String} repoName Repository name
                     */
                    repository.redirectToGitWeb = function(gitWebURL, repoName) {

                        if (gitWebURL) {
                            $window.open(gitWebURL, repoName + "GitWeb");
                        } else {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("The system could not find the repository’s [asis,Gitweb] [output,acronym,URL,Universal Resource Locator]."),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Opens repository path in file manager
                     * @method redirectToFileManager
                     * @param {String} fileManagerURL file Manager url for the repository path
                     * @param {String} repoName Repository name
                     */
                    repository.redirectToFileManager = function(fileManagerURL, repoName) {

                        if (fileManagerURL) {
                            $window.open(fileManagerURL, repoName + "FileManager");
                        } else {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("The system could not redirect you to the File Manager interface."),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Copies the repo's clone link to your machine's clipboard
                     * @method cloneToClipboard
                     * @param {String} cloneUrl The URL to be used to clone repos.
                     */
                    repository.cloneToClipboard = function(cloneUrl) {
                        try {
                            var result = versionControlService.cloneToClipboard(cloneUrl);
                            if (result) {
                                alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("The system successfully copied the “[_1]” clone [output,acronym,URL,Uniform Resource Locator] to the clipboard.", cloneUrl),
                                    closeable: true,
                                    replace: false,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });
                            }
                        } catch (error) {
                            alertService.add({
                                type: "danger",
                                message: _.escape(error),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     *  Checks if there are available branches or not.
                     *  @method hasAvailableBranches
                     *  @return {Boolean} Returns if there are any branches in the branchList.
                     */
                    repository.hasAvailableBranches = function() {
                        return Boolean(repository.branchList && repository.branchList.length !== 0);
                    };


                }
            ]
        );

        return controller;
    }
);
Back to Directory File Manager