Refactor and improve git repository cloning

Issue-ID: OOM-1803

Change-Id: Ia8e222c2b70b58ed1f7dbc4254ea2f5cb5a9ef7b
Signed-off-by: Milan Verespej <m.verespej@partner.samsung.com>
diff --git a/build/download/command_downloader.py b/build/download/command_downloader.py
new file mode 100755
index 0000000..5efc8b0
--- /dev/null
+++ b/build/download/command_downloader.py
@@ -0,0 +1,59 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#   COPYRIGHT NOTICE STARTS HERE
+
+#   Copyright 2019 © Samsung Electronics Co., Ltd.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+#   COPYRIGHT NOTICE ENDS HERE
+
+import logging
+import subprocess
+from abc import ABC
+from distutils.spawn import find_executable
+
+from downloader import AbstractDownloader
+
+log = logging.getLogger(__name__)
+
+
+class CommandDownloader(AbstractDownloader, ABC):
+    def __init__(self, list_type, cli_tool, *list_args):
+        super().__init__(list_type, *list_args)
+        if not find_executable(cli_tool):
+            raise FileNotFoundError(cli_tool)
+
+    def download(self):
+        """
+        Download items from list
+        """
+        if not self._initial_log():
+            return
+        items_left = len(self._missing)
+        error_occurred = False
+        for item, dst_dir in self._data_list.items():
+            try:
+                self._download_item((item, dst_dir))
+            except subprocess.CalledProcessError as err:
+                log.exception(err.output.decode())
+                error_occurred = True
+            items_left -= 1
+            log.info('{} {} left to download.'.format(items_left, self._list_type))
+        if error_occurred:
+            log.error('{} {} were not downloaded.'.format(items_left, self._list_type))
+            raise RuntimeError('One or more errors occurred')
+
+    def _download_item(self, item):
+        pass
diff --git a/build/download/git_downloader.py b/build/download/git_downloader.py
new file mode 100755
index 0000000..46faa8f
--- /dev/null
+++ b/build/download/git_downloader.py
@@ -0,0 +1,137 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#   COPYRIGHT NOTICE STARTS HERE
+
+#   Copyright 2019 © Samsung Electronics Co., Ltd.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+#   COPYRIGHT NOTICE ENDS HERE
+
+import argparse
+import datetime
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import timeit
+
+from command_downloader import CommandDownloader
+
+log = logging.getLogger(name=__name__)
+
+
+class GitDownloader(CommandDownloader):
+    def __init__(self, *list_args):
+        super().__init__('git repositories', 'git', *list_args)
+
+    @property
+    def check_table(self):
+        """
+        Table with information which items from lists are downloaded
+        """
+        self.missing()
+        header = ['Name', 'Branch', 'Downloaded']
+        return self._check_table(header, {'Name': 'l'},
+                                 ((*item.split(), self._downloaded(item)) for item
+                                  in self._data_list))
+
+    @staticmethod
+    def _download_item(item):
+        repo, branch = item[0].split()
+        dst = '{}/{}'.format(item[1], repo)
+        command = 'git clone -b {} --single-branch https://{} --bare {}'.format(branch,
+                                                                                repo,
+                                                                                dst)
+        if os.path.exists(dst):
+            log.warning('File or directory exists {} removing and cloning'
+                        ' to be sure it is latest.'.format(dst))
+            if os.path.isfile(dst):
+                os.remove(dst)
+            elif os.path.isdir(dst):
+                shutil.rmtree(dst)
+
+        log.info('Running: {}'.format(command))
+        log.info(
+            subprocess.check_output(command.split(), stderr=subprocess.STDOUT).decode())
+        log.info('Downloaded: {}'.format(repo))
+
+    def _is_missing(self, item):
+        """
+        Check if item is missing (not cloned)
+        :param item: item to check
+        :return: True if not present 'maybe' if directory exists
+        """
+        dst = '{}/{}'.format(self._data_list[item], item.split()[0])
+        if os.path.exists(dst):
+            # it is bare repo who knows
+            return 'maybe'
+        return True
+
+    def _downloaded(self, item):
+        """
+        Check if item is present (cloned)
+        :param item: item to check
+        :return: True if not cloned 'maybe' if directory exists
+        """
+        missing = self._is_missing(item)
+        if missing != 'maybe':
+            return False
+        # It is bare repo so who knows if it is latest version
+        return 'maybe'
+
+    def missing(self):
+        """
+        Check for missing data (not downloaded)
+        :return: dictionary of missing items
+        """
+        self._missing = {item: dst for item, dst in self._data_list.items()}
+        return self._missing
+
+
+def run_cli():
+    """
+    Run as cli tool
+    """
+    parser = argparse.ArgumentParser(description='Download git repositories from list')
+    parser.add_argument('git_list', metavar='git-list',
+                        help='File with list of npm packages to download.')
+    parser.add_argument('--output-dir', '-o', default=os.getcwd(),
+                        help='Download destination')
+    parser.add_argument('--check', '-c', action='store_true', default=False,
+                        help='Check mode')
+
+    args = parser.parse_args()
+
+    logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(message)s')
+
+    downloader = GitDownloader([args.git_list, args.output_dir])
+    if args.check:
+        log.info('Check mode. No download will be executed.')
+        log.info(downloader.check_table)
+        sys.exit(0)
+
+    timer_start = timeit.default_timer()
+    try:
+        downloader.download()
+    except RuntimeError:
+        sys.exit(1)
+    finally:
+        log.info('Downloading finished in {}'.format(
+            datetime.timedelta(seconds=timeit.default_timer() - timer_start)))
+
+
+if __name__ == '__main__':
+    run_cli()