diff --git a/web/client/codechecker_client/blame_info.py b/web/client/codechecker_client/blame_info.py
new file mode 100644
index 0000000000..ce3d128d84
--- /dev/null
+++ b/web/client/codechecker_client/blame_info.py
@@ -0,0 +1,111 @@
+import json
+import os
+import sys
+import zipfile
+
+from concurrent.futures import ProcessPoolExecutor
+from git import Repo
+from git.exc import InvalidGitRepositoryError
+from typing import Dict, List, Optional
+
+from codechecker_common.logger import get_logger
+
+LOG = get_logger('system')
+
+
+FileBlameInfo = Dict[str, Optional[Dict]]
+
+
+def __get_blame_info(file_path: str):
+    """ Get blame info for the given file. """
+    try:
+        repo = Repo(file_path, search_parent_directories=True)
+    except InvalidGitRepositoryError:
+        return
+
+    tracking_branch = None
+    try:
+        # If a commit is checked out, accessing the active_branch member will
+        # throw a type error. In this case we will use the current commit hash.
+        tracking_branch = str(repo.active_branch.tracking_branch())
+    except TypeError:
+        tracking_branch = repo.head.commit.hexsha
+
+    try:
+        blame = repo.blame_incremental(repo.head.commit.hexsha, file_path)
+
+        res = {
+            'version': 'v1',
+            'tracking_branch': tracking_branch,
+            'remote_url': next(repo.remote().urls, None),
+            'commits': {},
+            'blame': []}
+
+        for b in blame:
+            commit = b.commit
+
+            if commit.hexsha not in res['commits']:
+                res['commits'][commit.hexsha] = {
+                    'author': {
+                        'name': commit.author.name,
+                        'email': commit.author.email,
+                    },
+                    'summary': commit.summary,
+                    'message': commit.message,
+                    'committed_datetime': str(commit.committed_datetime)}
+
+            res['blame'].append({
+                'from': b.linenos[0],
+                'to': b.linenos[-1],
+                'commit': commit.hexsha})
+
+        LOG.debug("Collected blame info for %s", file_path)
+
+        return res
+    except Exception as ex:
+        LOG.debug("Failed to get blame information for %s: %s", file_path, ex)
+
+
+def __collect_blame_info_for_files(
+    file_paths: List[str],
+    zip_iter=map
+) -> FileBlameInfo:
+    """ Collect blame information for the given file paths. """
+    file_blame_info = {}
+    for file_path, blame_info in zip(file_paths,
+                                     zip_iter(__get_blame_info, file_paths)):
+        file_blame_info[file_path] = blame_info
+
+    return file_blame_info
+
+
+def assemble_blame_info(
+    zip_file: zipfile.ZipFile,
+    file_paths: List[str]
+) -> bool:
+    """
+    Collect and write blame information for the given files to the zip file.
+
+    Returns true if at least one blame information is collected.
+    """
+    # Currently ProcessPoolExecutor fails completely in windows.
+    # Reason is most likely combination of venv and fork() not
+    # being present in windows, so stuff like setting up
+    # PYTHONPATH in parent CodeChecker before store is executed
+    # are lost.
+    if sys.platform == "win32":
+        file_blame_info = __collect_blame_info_for_files(file_paths)
+    else:
+        with ProcessPoolExecutor() as executor:
+            file_blame_info = __collect_blame_info_for_files(
+                file_paths, executor.map)
+
+    # Add blame information to the zip for the files which will be sent
+    # to the server if exist.
+    for f, blame_info in file_blame_info.items():
+        if blame_info:
+            zip_file.writestr(
+                os.path.join('blame', f.lstrip('/')),
+                json.dumps(blame_info))
+
+    return any(v for v in file_blame_info.values())
diff --git a/web/client/codechecker_client/cmd/store.py b/web/client/codechecker_client/cmd/store.py
index d8ded1c114..3960256fd3 100644
--- a/web/client/codechecker_client/cmd/store.py
+++ b/web/client/codechecker_client/cmd/store.py
@@ -13,8 +13,6 @@
 
 import argparse
 import base64
-from git import Repo
-from git.exc import InvalidGitRepositoryError
 import hashlib
 import json
 import os
@@ -32,7 +30,6 @@
 from codechecker_api_shared.ttypes import RequestFailed, ErrorCode
 
 from codechecker_client import client as libclient
-
 from codechecker_common import arg, logger, plist_parser, util, cmd_config
 from codechecker_common.report import Report
 from codechecker_common.output import twodim
@@ -43,6 +40,11 @@
 from codechecker_web.shared import webserver_context, host_check
 from codechecker_web.shared.env import get_default_workspace
 
+try:
+    from codechecker_client.blame_info import assemble_blame_info
+except ImportError:
+    pass
+
 LOG = logger.get_logger('system')
 
 MAX_UPLOAD_SIZE = 1 * 1024 * 1024 * 1024  # 1GiB
@@ -581,66 +583,6 @@ def parse_report_files(report_files: Set[str], zip_iter=map):
             missing_source_files)
 
 
-def get_blame_info(file_path: str):
-    """ Get blame info for the given file. """
-    try:
-        repo = Repo(file_path, search_parent_directories=True)
-    except InvalidGitRepositoryError:
-        return
-
-    tracking_branch = None
-    try:
-        # If a commit is checked out, accessing the active_branch member will
-        # throw a type error. In this case we will use the current commit hash.
-        tracking_branch = str(repo.active_branch.tracking_branch())
-    except TypeError:
-        tracking_branch = repo.head.commit.hexsha
-
-    try:
-        blame = repo.blame_incremental(repo.head.commit.hexsha, file_path)
-
-        res = {
-            'version': 'v1',
-            'tracking_branch': tracking_branch,
-            'remote_url': next(repo.remote().urls, None),
-            'commits': {},
-            'blame': []}
-
-        for b in blame:
-            commit = b.commit
-
-            if commit.hexsha not in res['commits']:
-                res['commits'][commit.hexsha] = {
-                    'author': {
-                        'name': commit.author.name,
-                        'email': commit.author.email,
-                    },
-                    'summary': commit.summary,
-                    'message': commit.message,
-                    'committed_datetime': str(commit.committed_datetime)}
-
-            res['blame'].append({
-                'from': b.linenos[0],
-                'to': b.linenos[-1],
-                'commit': commit.hexsha})
-
-        LOG.debug("Collected blame info for %s", file_path)
-
-        return res
-    except Exception as ex:
-        LOG.debug("Failed to get blame information for %s: %s", file_path, ex)
-
-
-def collect_blame_info_for_files(file_paths: List[str], zip_iter=map):
-    """ Collect blame information for the given file paths. """
-    file_blame_info = {}
-    for file_path, blame_info in zip(file_paths,
-                                     zip_iter(get_blame_info, file_paths)):
-        file_blame_info[file_path] = blame_info
-
-    return file_blame_info
-
-
 def assemble_zip(inputs, zip_file, client):
     """Collect and compress report and source files, together with files
     contanining analysis related information into a zip file which
@@ -751,26 +693,18 @@ def assemble_zip(inputs, zip_file, client):
                 except KeyError:
                     zipf.write(f, file_path)
 
-        # Currently ProcessPoolExecutor fails completely in windows.
-        # Reason is most likely combination of venv and fork() not
-        # being present in windows, so stuff like setting up
-        # PYTHONPATH in parent CodeChecker before store is executed
-        # are lost.
-        if sys.platform == "win32":
-            file_blame_info = collect_blame_info_for_files(
-                collected_file_paths)
-        else:
-            with ProcessPoolExecutor() as executor:
-                file_blame_info = collect_blame_info_for_files(
-                    collected_file_paths, executor.map)
-
-        # Add blame information to the zip for the files which will be sent to
-        # the server if exist.
-        for f, blame_info in file_blame_info.items():
-            if blame_info:
-                zipf.writestr(
-                    os.path.join('blame', f.lstrip('/')),
-                    json.dumps(blame_info))
+        if collected_file_paths:
+            LOG.info("Collecting blame information for source files...")
+            try:
+                if assemble_blame_info(zipf, collected_file_paths):
+                    LOG.info("Collecting blame information done.")
+                else:
+                    LOG.info("No blame information found for source files.")
+            except NameError:
+                LOG.warning(
+                    "Collecting blame information has been failed. Make sure "
+                    "'git' is available on your system to hide this warning "
+                    "message.")
 
         zipf.writestr('content_hashes.json', json.dumps(file_to_hash))